Testing helps us identify any defects or errors that may have been made during development. But it takes time and resources, especially in Android where it requires the installation of many dependencies and a device to perform UI tests.
By using a Docker container, we can build and run tests for multiple feature branches, speeding up the development and increasing productivity.
In this tutorial, we’re going to learn how to build a lightweight Android container to isolate the testing process.
No Android Studio/GUI applications required.
Android emulator runs on a Docker container.
Has ability to cache dependencies for later build.
Wipe out everything after the process.
1. Start docker container
The image that we build on top of is: ubuntu:latest
Assuming you have docker installed (if not, please follow this link), you can run this to start docker container:
$ docker run --privileged -dit --name android-container ubuntu
—
privileged
: grant permission to launch VM on container.—
it
: interactively execute shell cmd.—
d
: run container in the background.—
name android-container
: container’s name, will use later to attach and commit docker image.
(Optional) To run docker as non-root, the simplest way is adding the current user to group docker
$ sudo groupadd docker // Add group docker if it doesn't already exist
$ sudo gpasswd -a $USER docker // Add current user to group docker
$ newgrp docker // reload (or you can re-login to reload)
$ docker run hello-world // check if it works
2. Install SDK packages
Prerequisites:
Make sure you install the following dependencies. Otherwise, you may notice No such file or directory when installing android SDK or start emulator
Please double-check the redundant dependencies.🙇 (ex: vim — if you’re not a fan)
$ apt update && apt install -y openjdk-8-jdk vim git unzip libglu1 libpulse-dev libasound2 libc6 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxi6 libxtst6 libnss3 wget
Gradle
Install gradle
and gradle-wrapper
. We’ll use version 5.4.1
in this tutorial. No worries, it can be updated with build-arg
later 😊
Download gradle-5.4.1
to /tmp/gradle-5.4.1
and unzip the content within /opt/gradle
$ wget https://services.gradle.org/distributions/gradle-5.4.1-bin.zip -P /tmp \
&& unzip -d /opt/gradle /tmp/gradle-5.4.1-bin.zip
Make a new directory /opt/gradlew
and install gradle-wrapper
there. (The directory names can be anything, but save the files to somewhere that easy to find)
$ mkdir /opt/gradlew \
&& /opt/gradle/gradle-5.4.1/bin/gradle wrapper --gradle-version 5.4.1 --distribution-type all -p /opt/gradlew \
&& /opt/gradle/gradle-5.4.1/bin/gradle wrapper -p /opt/gradlew
Android SDK
You will need to download SDK manually without Android Studio bundled, SDK tools only, check the link here to get the URL.
Like Gradle, we’ll save it to /tmp
and get it extracted in /opt
$ wget 'https://dl.google.com/android/repository/sdk-tools-linux-4333796.zip' -P /tmp \
&& unzip -d /opt/android /tmp/sdk-tools-linux-4333796.zip \
The Android software development kit (SDK) includes different components, including SDK Tools, Build Tools, and Platform Tools. The SDK Tools primarily includes the stock Android emulator, hierarchy viewer, SDK manager, and ProGuard. The Build Tools primarily include aapt (Android packaging tool to create .APK), dx (Android tool that converts .java files to .dex files). Platform Tools include the Android debug shell, sqlite3 and Systrace.
Most important packages are platform-tools
, tools
and emulator
. Run this to install them quickly:
$ yes Y | /opt/android/tools/bin/sdkmanager --install "platform-tools" "system-images;android-*28*;google_apis;x86" "platforms;android-28" "build-tools;28.0.3" "emulator"
platform-tools
containsadb
tools
containsavdmanager
andsdkamanager
emulator
: run emulatorsystem-images;android-*28*;google_apis;x86
: use to create avd
Accept all licenses of Android SDK
$ yes Y | /opt/android/tools/bin/sdkmanager --licenses
Seems good, now create an avd test
$ echo "no" | /opt/android/tools/bin/avdmanager --verbose create avd --force --name "test" --device "pixel" --package "system-images;android-28;google_apis;x86" --tag "google_apis" --abi "x86"
— name: device’s name.
— abi: CPU architecture.
— tag
google_api
: support Google API.
Check one more time to see if it works!
$ /opt/android/emulator/emulator -list-avds
# Expected Result: test
3. Set up environment variables
Edit your .bashrc
or any config files that you’re familiar with:
GRADLE_HOME=/opt/gradle/gradle-5.4.1
ANDROID_HOME=/opt/android
PATH=$PATH:$GRADLE_HOME/bin:/opt/gradlew:$ANDROID_HOME/emulator:$ANDROID_HOME/tools/bin:$ANDROID_HOME/platform-tools
LD_LIBRARY_PATH= $ANDROID_HOME/emulator/lib64:$ANDROID_HOME/emulator/lib64/qt/lib
Then reflect the changes with source ~/.bashrc
Note: The order of PATH variable is important because android provides 2 executed file for emulator
, one is packaged in /android/emulator
and another is in /android/tools/bin
. We’ll use /android/emulator
to run android device.
More detail is this SO answer.
4. Run emulator
Here are some minor steps before running emulator:
Stop any running emulators with ADB.
Start emulator in the background with flag
-no-window
and-gpu off
Turn off animation to avoid flaky tests.
Some test cases will perform an action on a view that might be not visible on a small screen — so we set the emulator up to have a high resolution (1440x2880)
function wait_emulator_to_be_ready() {
adb devices | grep emulator | cut -f1 | while read line; do adb -s $line emu kill; done
emulator -avd test -no-audio -no-boot-anim -no-window -accel on -gpu off -skin 1440x2880 &
boot_completed=false
while [ "$boot_completed" == false ]; do
status=$(adb wait-for-device shell getprop sys.boot_completed | tr -d '\r')
echo "Boot Status: $status"
if [ "$status" == "1" ]; then
boot_completed=true
else
sleep 1
fi
done
}
function disable_animation() {
adb shell "settings put global window_animation_scale 0.0"
adb shell "settings put global transition_animation_scale 0.0"
adb shell "settings put global animator_duration_scale 0.0"
}
wait_emulator_to_be_ready
sleep 1
disable_animation
Start the emulator with sh start.sh
or ./start.sh
and wait until Boot Status: 1
which means the device is fully loaded and ready to use.
Check again by enter bg
to see the background jobs.
$ ./start.sh
emulator: WARNING: Your AVD has been configured with an in-guest renderer, but the system image does not support guest rendering.Falling back to 'swiftshader_indirect' mode.
WARNING: change of renderer detected.
checkValid: hw configs not eq
emulator: Cold boot: different AVD configuration
Your emulator is out of date, please update by launching Android Studio:
- Start Android Studio
- Select menu "Tools > Android > SDK Manager"
- Click "SDK Tools" tab
- Check "Android Emulator" checkbox
- Click "OK"
Boot Status:
Boot Status:
Boot Status:
Boot Status:
Boot Status:
Boot Status:
Boot Status:
Boot Status:
Boot Status:
Boot Status: 1
emulator: INFO: boot completed
emulator: INFO: boot time 13200 ms
emulator: Increasing screen off timeout, logcat buffer size to 2M.
emulator: Revoking microphone permissions for Google App.
Check running emulators
$ adb devices
List of devices attached
emulator-5554 device
We have an Android emulator running in the container successfully!
5. Build docker image
Open a new terminal tab, stop android-container
and commit your changes to create a new Docker image:
$ docker stop android-container && docker commit android-container android-container:v1
Test once again:
$ docker images
# Expected result: REPOSITORY TAG
android-container v1
For further extending with more configurations (ex: Flutter), let’s wrap things up and finalize the Dockerfile
FROM ubuntu
LABEL maintainer "codecaigicungduoc@gmail"
WORKDIR /
SHELL ["/bin/bash", "-c"]
RUN apt update && apt install -y openjdk-8-jdk vim git unzip libglu1 libpulse-dev libasound2 libc6 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxi6 libxtst6 libnss3 wget
ARG GRADLE_VERSION=5.4.1
ARG ANDROID_API_LEVEL=28
ARG ANDROID_BUILD_TOOLS_LEVEL=28.0.3
ARG EMULATOR_NAME='test'
RUN wget https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-bin.zip -P /tmp \
&& unzip -d /opt/gradle /tmp/gradle-${GRADLE_VERSION}-bin.zip \
&& mkdir /opt/gradlew \
&& /opt/gradle/gradle-${GRADLE_VERSION}/bin/gradle wrapper --gradle-version ${GRADLE_VERSION} --distribution-type all -p /opt/gradlew \
&& /opt/gradle/gradle-${GRADLE_VERSION}/bin/gradle wrapper -p /opt/gradlew
RUN wget 'https://dl.google.com/android/repository/sdk-tools-linux-4333796.zip' -P /tmp \
&& unzip -d /opt/android /tmp/sdk-tools-linux-4333796.zip \
&& yes Y | /opt/android/tools/bin/sdkmanager --install "platform-tools" "system-images;android-${ANDROID_API_LEVEL};google_apis;x86" "platforms;android-${ANDROID_API_LEVEL}" "build-tools;${ANDROID_BUILD_TOOLS_LEVEL}" "emulator" \
&& yes Y | /opt/android/tools/bin/sdkmanager --licenses \
&& echo "no" | /opt/android/tools/bin/avdmanager --verbose create avd --force --name "test" --device "pixel" --package "system-images;android-${ANDROID_API_LEVEL};google_apis;x86" --tag "google_apis" --abi "x86"
ENV GRADLE_HOME=/opt/gradle/gradle-$GRADLE_VERSION \
ANDROID_HOME=/opt/android
ENV PATH "$PATH:$GRADLE_HOME/bin:/opt/gradlew:$ANDROID_HOME/emulator:$ANDROID_HOME/tools/bin:$ANDROID_HOME/platform-tools"
ENV LD_LIBRARY_PATH "$ANDROID_HOME/emulator/lib64:$ANDROID_HOME/emulator/lib64/qt/lib"
ADD start.sh /
RUN chmod +x start.sh
6. Build project and run tests
Check out your project configurations and build the ocker image with appropriate arguments:
$ docker build \
--build-arg GRADLE_VERSION=5.4.1 \
--build-arg ANDROID_API_LEVEL=28 \
--build-arg ANDROID_BUILD_TOOLS_LEVEL=28.0.3 \
--build-arg EMULATOR_NAME=test \
-t android-container .
Enter the top level of your project directory and run:
docker run --privileged -it --rm -v $PWD:/data android-container bash -c ". /start.sh && gradlew build -p /data"
-
it
: interactive mode.—
rm
: remove volume after the process is completed.-v
$PWD:/data
: mount your directory into the container:/databash
-c “ . /start.sh && gradlew build -p /data”
: start the emulator and build project.
Voilà! We have the whole testing process run independently on docker container. Let’s get back to other stuff while waiting for the report. 😝 😝
For more detailed about build steps and sample, please check out this GitHub repo. I’ve made a cool sample with Sunflower project.
If you have any questions, feel free to ask, reach out to me via codecaigicungduoc@gmail.com or creating a new issue on my github repo.
Thanks for reading 🙇 🙇 🙇
Top comments (4)
Great instructions. I can start the emulator in the docker. However, I have one issue, I cannot access the network inside the avd.
I verified there is internet connection for the docker. But no internet connection
root@f42debaf16da:~# adb shell ping 8.8.8.8
connect: Network is unreachable
I tried to add -http-proxy 172.17.0.1:8080 or -dns-server 8.8.8.8 when run the emulator. But none of them work.
How can you have the internet connect for the emulator.
Tried this in my mac air laptop (early 2014) and is stuck at emulator error. Will this setup work in a laptop ?. What other stuff do i need in the container (KVM etc ?) to make it work ?
~$docker run --privileged -it 67d6089da088 bash -c ". /start.sh"
Ubuntu 18.04.4 LTS \n \l
You will need to do this under virtualbox. I dont have a mac, but it can be made to work, a colegue has it set up to run I believe.
ANDROID_HOME is deprecated, I see an updated script on github, care to walk us through the update? Struggling a little with the emulator when I upgrade SDK and then try use emulator afterwards.