Android系统编译并打包成容器镜像的原理详解
Android 系统容器化深度解析:编译、打包、运行, 构建轻量级 Android 虚拟化环境
想在 Linux 环境下运行 Android 系统, 摆脱传统模拟器的性能束缚? 本文带你深入 Android 系统容器化原理, 详细解读 remote-android 项目架构、 源码整合、 编译打包、 镜像制作全流程, 并提供 Docker 运行参数详解, 助你轻松打造高效、 轻量级的 Android 虚拟化环境!
关键词: Android, Linux, 容器化, Docker, remote-android, 源码编译, 镜像制作, 虚拟化, ARM 指令转译, 系统启动
一、背景分析
之所以能够把Android打包成docker,主要还是由于Android建立在Linux kernel之上。我们知道,容器技术实现的基础是chroot+namespace+cgroups,这些功能都是由内核直接提供的。那么从理论上而言,将Android的init的进程以容器的形式跑在一个独立的名称空间中是完全可行的。
此时Android的最底层的kernel将共享宿主机的kernel:
当宿主机kernel运行在ARM处理器时,容器在CPU及内存层面几乎没有损耗。
当宿主机kernel运行在X86处理器时,需转义成ARM指令集,存在损耗。
Android的kernel与桌面的kernel相比,做了一定的定制和优化,比如:
增加了移动设备所需的特定驱动程序和功能的支持。
针对移动设备特定功能的补丁和改进,以提高功耗效率、性能和与移动硬件的兼容性。
同时对于内核进行了扩展,比如:
增加了Binder,Android系统中的进程间通信(IPC)机制,用于不同应用程序组件之间的通信。它允许应用程序在不同的进程之间传递数据,执行远程调用等。这种机制对于Android的组件化架构和应用间通信非常关键。
增加了Ashmem,Android Shared Memory,用于共享匿名内存的机制。它允许不同的应用程序共享内存,这对于共享数据和内存映射文件对Android系统的性能和资源管理非常重要。
从实际运行的角度而言,我们只需要保证我们的X86 server系统,如Ubuntu的运行中的内核,包含了Android内核所需的特定模块,就能确保Android系统能在主机内核的基础上跑起来。
二、remote-android项目分析
这个项目的文档支撑不是很好,对于Android framework部分几乎没有谈及,只是对于使用部分进行了简单的介绍。好在repo的整体架构比较清晰。
说明部分,含部分操作代码:
Framework部分:
Android patch脚本:
X86 to ARM指令转义:
docker build部分:
三、Android系统源码整合
Android系统源码极其庞大,使用传统的git进行组织及管理已经不现实。因此google开发了repo工具进行代码的辅助管理。
建议直接下载google官方最新的repo工具并放置到PATH中。通过apt安装的版本可能不支持最新的功能。
link:https://storage.googleapis.com/git-repo-downloads/repo
源码的下载,Android的源码大小已经达到了上百G,原始存储在https://android.googlesource.com这个网站,已经被墙。因此想通过VPN直接下载下来不是很现实。同时,我们在进行repo同步前,还有可能对于相关的依赖进行二次的裁剪及增加,如通过VPN控制,会使得整个过程变得比较复杂。
因此建议将最原始的google/LineageOS的源替换成清华源,其余的部分不会非常大,可以继续使用VPN拉取,确保稳定。
cat >> ~/.gitconfig <<EOF
[url "https://mirrors.tuna.tsinghua.edu.cn/git/git-repo"]
insteadof = https://gerrit.googlesource.com/git-repo
[url "https://mirrors.tuna.tsinghua.edu.cn/git/lineageOS/"]
insteadof = https://review.lineageos.org/
[url "https://mirrors.tuna.tsinghua.edu.cn/git/AOSP/"]
insteadof = https://android.googlesource.com/
[url "https://mirrors.tuna.tsinghua.edu.cn/git/lineageOS/LineageOS/"]
insteadof = https://github.com/LineageOS/
EOF
源码下载完成之后就可以进行正常的framework的开发、优化、配置,最终编译。
这个部分是整个项目最核心的部分,需要Android framework工程师参与,一般的业务开发可能还不行。
redroid的源码准备过程:
# fetch code
#####################
mkdir ~/redroid && cd ~/redroid
# check supported branch in https://github.com/remote-android/redroid-patches.git
repo init -u https://android.googlesource.com/platform/manifest --git-lfs --depth=1 -b android-11.0.0_r48
# add local manifests
git clone https://github.com/remote-android/local_manifests.git ~/redroid/.repo/local_manifests -b 11.0.0
# sync code
repo sync -c
# apply redroid patches
git clone https://github.com/remote-android/redroid-patches.git ~/redroid-patches
~/redroid-patches/apply-patch.sh ~/redroid
四、Android系统的编译过程
redroid基于docker容器进行Anroid系统的打包,打包时会临时启动一个交互式接口的容器。
创建build容器:
git clone <https://github.com/remote-android/redroid-doc>
cd android-builder-docker/
sudo docker build --build-arg userid=$(id -u) --build-arg groupid=$(id -g) --build-arg username=$(id -un) -t redroid-builder .
整个容器的重点创建了与宿主机完全一样的uid/gid的用户,并完成Android系统编译所需依赖包的安装,最终使用与宿主机一致的用户chroot到该容器内,开展进一步的编译。
特别注意,这个容器仅仅用作编译使用,与后期android容器的制作无关。同时,理论上直接使用宿主机编译也是可以的。
raw Dockerfile:
FROM ubuntu:20.04
ARG userid
ARG groupid
ARG username
# COPY apt.conf /etc/apt/apt.conf
# COPY sources.list etc/apt/sources.list
ENV DEBIAN_FRONTEND noninteractive
RUN apt-get update \\
&& echo "install package for building AOSP" \\
&& apt-get install -y git-core gnupg flex bison build-essential zip curl zlib1g-dev \\
gcc-multilib g++-multilib libc6-dev-i386 libncurses5 lib32ncurses5-dev x11proto-core-dev \\
libx11-dev lib32z1-dev libgl1-mesa-dev libxml2-utils xsltproc unzip fontconfig \\
&& echo "install utils" \\
&& apt-get install -y sudo rsync \\
&& echo "install packages for build mesa3d or meson related" \\
&& apt-get install -y python3-pip pkg-config python3-dev ninja-build \\
&& pip3 install mako meson \\
&& echo "packages for legacy mesa3d (< 22.0.0)" \\
&& apt-get install -y python2 python-mako python-is-python2 python-enum34 gettext
RUN groupadd -g $groupid $username \\
&& useradd -m -u $userid -g $groupid $username \\
&& echo "$username ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers \\
&& echo $username >/root/username \\
&& echo "$username:$username" | chpasswd && adduser $username sudo
ENV HOME=/home/$username \\
USER=$username \\
PATH=/src/.repo/repo:/src/prebuilts/jdk/jdk8/linux-x86/bin/:$PATH
ENTRYPOINT chroot --userspec=$(cat /root/username):$(cat /root/username) / /bin/bash -i
Android系统编译:
# start builder
#####################
docker run -it --rm --hostname redroid-builder --name redroid-builder -v ~/redroid:/src redroid-builder
####################
# build redroid
#####################
cd /src
. build/envsetup.sh
lunch redroid_x86_64-userdebug
# redroid_arm64-userdebug
# redroid_x86_64_only-userdebug (64 bit only, redroid 12+)
# redroid_arm64_only-userdebug (64 bit only, redroid 12+)
# start to build
m
五、容器镜像制作
在讲容器镜像制作之前,简单的对于linux的系统启动流程进行一下回顾,不然其实比较难以理解OS镜像制作的本质是什么。
一个传统的linux操作系统的启动主要由以下部分组成:
POST → (legacy/UEFI) BIOS → 按照顺序找到启动介质 → 找到bootloader (MBR/GPT) → kernel(ramdisk/ramfs) → rootfs(只读) → switchroot(chroot) → init
当我们使用容器的方式启动一个系统时,kernel是已经启动的状态,并且已经加载所需的模块。启动容器时,会调用kernel中namespace相关的系统调用,创建一个隔离的名称空间,挂载文件目录,chroot后init即可。
因此,创建一个OS镜像的本质就是制定一个根文件系统并指定该文件系统的init程序,也就是Android系统的init。
理解之后,就可以进行镜像的制作。
# create redroid image in *HOST*
#####################
cd ~/redroid/out/target/product/redroid_x86_64
sudo mount system.img system -o ro
sudo mount vendor.img vendor -o ro
sudo tar --xattrs -c vendor -C system --exclude="vendor" . |sudo docker import -c 'ENTRYPOINT ["/init", "androidboot.hardware=redroid"]' - redroid
sudo umount system vendor
# create rootfs only image for develop purpose
tar --xattrs -c -C root . | docker import -c 'ENTRYPOINT ["/init", "androidboot.hardware=redroid"]' - redroid-dev
此处使用tar及docker import指令相结合的方式,将Android系统的文件目录打包成基础镜像,将Androidinit指定为ENTRYPOINT,这样就可以在启动时方便的接收参数。
cd C:\\Users\\Administrator\\Desktop\\scrcpy-win64-v2.0
.\\adb.exe connect 192.168.100.199:5555
.\\scrcpy.exe -s 192.168.100.199:5555