通过 x86_64 主机为 Macmini M4 sdkmanager emulator 编译 LineageOS 23.2 Android 16 镜像并识别运行
#docker,#linux,#android,#kernel,#lineageos,#git,#android16,#sdkmanager, #avdmanager, #avd, #emulator, #android, #fastboot, #adb, #openjdk, #java
- gmkm6 x86_64 主机配置环境
- 安装配置 docker 防止污染环境
- 更新 docker 插件 compose
- 更新 docker 插件 buildx
- 创建编译容器
- 容器内编译 lineageos 镜像
- 传输 lineage 镜像包到 Macmikni M4 设备
- 搭建 android sdkmanager 环境
- sdkmanager 命令
- 拼接解压
- 配置临时环境安装必备组件
- sdkmanager 关联 lineage 镜像
- 测试运行 lineage 虚拟镜像
- 参考&感谢
gmkm6 x86_64 主机配置环境
gmk m6 配置是 24g 12c 1T+1T Ubuntu 24.04.2 LTS 实际运行内存 20G
由于内存不足也意味着需要 swap 扩展补充 45G 以满足编译 android 16 的条件 >=64G
实际编译时常 09:30:10
临时配置swap在内存不足的时候可以借磁盘空间,磁盘因此降低写入
# 查看当前swap启用所使用的文件假设为 /swap.img
sudo swapon --show
# 查看当前内存状态
sudo free -h
# 关闭swap文件占用
sudo swapoff /swap.img
# 差不多有>=64G就行
sudo fallocate -l 45G /swap.img
sudo du -sh /swap.img
sudo chmod 600 /swap.img
sudo mkswap /swap.img
sudo swapon /swap.img
sudo free -h
如果你想开机自动挂载 swap 则固化到 /etc/fstab(可选)
sudo nano /etc/fstab
追加以下内容
/swap.img none swap sw 0 0
安装配置 docker 防止污染环境
由于不想污染本地宿主机环境,我打算安装 docker 环境,在 docker 中编译 android 16 镜像
# 系统可以使用官方一键安装脚本 https://github.com/docker/docker-install
curl -fsSL https://test.docker.com -o test-docker.sh
sh test-docker.sh
# Manage Docker as a non-root user
## 非 root 用户需要加入到 docker 组才有权限使用
# Create the docker group
## 添加 docker 组
sudo groupadd docker
# Add your user to the docker group.
## 将当前用户加入到 docker 组权限
sudo usermod -aG docker ${USER}
# Log out and log back in so that your group membership is re-evaluated.
## 临时进入 docker 组测试,更好的方式是退出并重新登录测试
newgrp docker
# Configure Docker to start on boot
# 启用 docker 开机自启动服务
sudo systemctl enable docker.service
sudo systemctl enable containerd.service
# satrt
# 开启 docker 服务,其实上一步就启用了
sudo systemctl start docker.service
sudo systemctl start containerd.service
# Verify that Docker Engine is installed correctly by running the hello-world image
# 测试 docker hello-world:latest 打印
docker run --rm hello-world:latest
更新 docker 插件 compose
compose 也更新一下过程如下
# GitHub 项目 URI
URI="docker/compose"
# 获取最新版本
VERSION=$(curl -sL "https://github.com/${URI}/releases" | grep -Eo '/releases/tag/[^"]+' | awk -F'/tag/' '{print $2}' | head -n 1)
echo "Latest version: ${VERSION}"
# 获取操作系统和架构信息
OS=$(uname -s)
ARCH=$(uname -m)
# 映射平台到官方命名
case "${OS}" in
Linux)
PLATFORM="linux"
if [[ "${ARCH}" == "arm64" || "${ARCH}" == "aarch64" ]]; then
ARCH="aarch64"
elif [[ "${ARCH}" == "x86_64" ]]; then
ARCH="x86_64"
else
echo "Unsupported architecture: ${ARCH}"
echo 'should exit 1'
fi
;;
*)
echo "Unsupported OS: ${OS}"
echo 'should exit 1'
;;
esac
# 输出最终平台和架构
echo "Platform: ${PLATFORM}"
echo "Architecture: ${ARCH}"
# 拼接下载链接和校验码链接
TARGET_FILE="docker-compose-${PLATFORM}-${ARCH}"
SHA256_FILE="${TARGET_FILE}.sha256"
URI_DOWNLOAD="https://github.com/${URI}/releases/download/${VERSION}/${TARGET_FILE}"
URI_SHA256="https://github.com/${URI}/releases/download/${VERSION}/${SHA256_FILE}"
echo "Download URL: ${URI_DOWNLOAD}"
echo "SHA256 URL: ${URI_SHA256}"
# 检查文件是否存在
if [[ -f "/tmp/${TARGET_FILE}" ]]; then
echo "File already exists: /tmp/${TARGET_FILE}"
# 删除旧的 SHA256 文件(如果存在)
if [[ -f "/tmp/${SHA256_FILE}" ]]; then
echo "Removing old SHA256 file: /tmp/${SHA256_FILE}"
rm -fv "/tmp/${SHA256_FILE}"
fi
# 下载新的 SHA256 文件
echo "Downloading SHA256 file..."
curl -L -C - --retry 3 --retry-delay 5 --progress-bar -o "/tmp/${SHA256_FILE}" "${URI_SHA256}"
# 校验文件完整性
# shasum 校验依赖 perl 可能 linux 系统需要手动安装
echo "Verifying file integrity for /tmp/${TARGET_FILE}..."
cd /tmp
if ! shasum -a 256 -c "${SHA256_FILE}"; then
log_warning "SHA256 checksum failed. Removing file and retrying..."
rm -fv "/tmp/${TARGET_FILE}"
else
echo "File integrity verified successfully."
fi
fi
# 如果文件不存在或之前校验失败
if [[ ! -f "/tmp/${TARGET_FILE}" ]]; then
echo "Downloading file..."
curl -L -C - --retry 3 --retry-delay 5 --progress-bar -o "/tmp/${TARGET_FILE}" "${URI_DOWNLOAD}"
# 删除旧的 SHA256 文件并重新下载
if [[ -f "/tmp/${SHA256_FILE}" ]]; then
echo "Removing old SHA256 file: /tmp/${SHA256_FILE}"
rm -fv "/tmp/${SHA256_FILE}"
fi
echo "Downloading SHA256 file..."
curl -L --progress-bar -o "/tmp/${SHA256_FILE}" "${URI_SHA256}"
# 校验完整性
# shasum 校验依赖 perl 可能 linux 系统需要手动安装
echo "Verifying file integrity for /tmp/${TARGET_FILE}..."
cd /tmp
if ! shasum -a 256 -c "${SHA256_FILE}"; then
echo "Download failed: SHA256 checksum does not match."
echo 'should exit 1'
else
echo "File integrity verified successfully."
fi
fi
sudo mv -fv "/tmp/${TARGET_FILE}" /usr/local/bin/docker-compose
# Apply executable permissions to the binary
## 赋予执行权
sudo chmod -v +x /usr/local/bin/docker-compose
# create a symbolic link to /usr/libexec/docker/cli-plugins/
# 创建插件目录和软链接
sudo mkdir -pv /usr/libexec/docker/cli-plugins/
sudo ln -sfv /usr/local/bin/docker-compose /usr/libexec/docker/cli-plugins/docker-compose
# Test the installation.
## 测试版本打印
docker-compose version
docker compose version
更新 docker 插件 buildx
buildx 也更新一下过程如下
# GitHub 项目 URI
URI="docker/buildx"
# 获取最新版本
VERSION=$(curl -sL "https://github.com/${URI}/releases" | grep -Eo '/releases/tag/[^"]+' | awk -F'/tag/' '{print $2}' | head -n 1)
echo "Latest version: ${VERSION}"
# 获取操作系统和架构信息
OS=$(uname -s)
ARCH=$(uname -m)
# 映射平台到官方命名
case "${OS}" in
Linux)
PLATFORM="linux"
if [[ "${ARCH}" == "arm64" || "${ARCH}" == "aarch64" ]]; then
ARCH="arm64"
elif [[ "${ARCH}" == "x86_64" ]]; then
ARCH="amd64"
else
echo "Unsupported architecture: ${ARCH}"
echo 'should exit 1'
fi
;;
*)
echo "Unsupported OS: ${OS}"
echo 'should exit 1'
;;
esac
# 输出最终平台和架构
echo "Platform: ${PLATFORM}"
echo "Architecture: ${ARCH}"
# 拼接下载链接和校验码链接
TARGET_FILE="buildx-${VERSION}.${PLATFORM}-${ARCH}"
SHA256_FILE="${TARGET_FILE}.sbom.json"
URI_DOWNLOAD="https://github.com/${URI}/releases/download/${VERSION}/${TARGET_FILE}"
URI_SHA256="https://github.com/${URI}/releases/download/${VERSION}/${SHA256_FILE}"
echo "Download URL: ${URI_DOWNLOAD}"
echo "SHA256 URL: ${URI_SHA256}"
# 检查文件是否存在
if [[ -f "/tmp/${TARGET_FILE}" ]]; then
echo "File already exists: /tmp/${TARGET_FILE}"
# 删除旧的 SHA256 文件(如果存在)
if [[ -f "/tmp/${SHA256_FILE}" ]]; then
echo "Removing old SHA256 file: /tmp/${SHA256_FILE}"
rm -fv "/tmp/${SHA256_FILE}"
fi
# 下载新的 SHA256 文件
echo "Downloading SHA256 file..."
curl -L -C - --retry 3 --retry-delay 5 --progress-bar -o "/tmp/${SHA256_FILE}" "${URI_SHA256}"
# 提取校验码
CHECKSUM=$(cat "/tmp/${SHA256_FILE}" | jq -r --arg filename "${TARGET_FILE}" '.subject[] | select(.name == $filename) | .digest.sha256')
# 将校验码写入源文件
echo "${CHECKSUM} *${TARGET_FILE}" > "/tmp/${SHA256_FILE}"
echo "校验码 ${CHECKSUM} 已写入文件: /tmp/${SHA256_FILE}"
# 校验文件完整性
# shasum 校验依赖 perl 可能 linux 系统需要手动安装
echo "Verifying file integrity for /tmp/${TARGET_FILE}..."
cd /tmp
if ! shasum -a 256 -c "${SHA256_FILE}"; then
log_warning "SHA256 checksum failed. Removing file and retrying..."
rm -fv "/tmp/${TARGET_FILE}"
else
echo "File integrity verified successfully."
fi
fi
# 如果文件不存在或之前校验失败
if [[ ! -f "/tmp/${TARGET_FILE}" ]]; then
echo "Downloading file..."
curl -L -C - --retry 3 --retry-delay 5 --progress-bar -o "/tmp/${TARGET_FILE}" "${URI_DOWNLOAD}"
# 删除旧的 SHA256 文件并重新下载
if [[ -f "/tmp/${SHA256_FILE}" ]]; then
echo "Removing old SHA256 file: /tmp/${SHA256_FILE}"
rm -fv "/tmp/${SHA256_FILE}"
fi
echo "Downloading SHA256 file..."
curl -L --progress-bar -o "/tmp/${SHA256_FILE}" "${URI_SHA256}"
# 提取校验码
CHECKSUM=$(cat "/tmp/${SHA256_FILE}" | jq -r --arg filename "${TARGET_FILE}" '.subject[] | select(.name == $filename) | .digest.sha256')
# 将校验码写入源文件
echo "${CHECKSUM} *${TARGET_FILE}" > "/tmp/${SHA256_FILE}"
echo "校验码 ${CHECKSUM} 已写入文件: /tmp/${SHA256_FILE}"
# 校验完整性
# shasum 校验依赖 perl 可能 linux 系统需要手动安装
echo "Verifying file integrity for /tmp/${TARGET_FILE}..."
cd /tmp
if ! shasum -a 256 -c "${SHA256_FILE}"; then
echo "Download failed: SHA256 checksum does not match."
echo 'should exit 1'
else
echo "File integrity verified successfully."
fi
fi
sudo mv -fv "/tmp/${TARGET_FILE}" /usr/local/bin/docker-buildx
# Apply executable permissions to the binary
## 赋予执行权
sudo chmod -v +x /usr/local/bin/docker-buildx
# create a symbolic link to /usr/libexec/docker/cli-plugins/
# 创建插件目录和软链接
sudo mkdir -pv /usr/libexec/docker/cli-plugins/
sudo ln -sfv /usr/local/bin/docker-buildx /usr/libexec/docker/cli-plugins/docker-buildx
# Test the installation.
## 测试版本打印
docker-buildx version
docker buildx version
创建编译容器
创建目录,创建启动容器并挂载,进入容器
其中一块磁盘已经牺牲读写性能用于 swap 了
那么就用另一块磁盘存储源码负责编译读写
当然最痛苦的就是cpu了,这么多磁盘读写cpu一定会满
另一块 1T SSD 打算用路径 /media/gmktecm6/KINGSTONSSD1T/oneplus7pro/android-build
sudo mkdir -pv /media/gmktecm6/KINGSTONSSD1T/oneplus7pro/android-build
docker run --restart=always --platform linux/amd64 --privileged \
--name android-build -d -e DEBIAN_FRONTEND=noninteractive \
-it -v /media/gmktecm6/KINGSTONSSD1T/oneplus7pro:/root/workspace \
-v /dev/bus/usb:/dev/bus/usb \
docker.io/library/ubuntu:22.04 \
bash
docker exec -it android-build bash
容器内编译 lineageos 镜像
容器内编译操作
# 安装编译环境依赖
DEBIAN_FRONTEND=noninteractive
apt update
apt -y install apt-utils autoconf automake axel bc \
bison build-essential ccache clang cmake \
curl expat file flex g++ \
g++-multilib gawk gcc gcc-multilib git \
git-lfs gnupg gperf htop imagemagick \
lib32ncurses-dev lib32readline-dev lib32z1-dev libc6-dev libcap-dev \
libdw-dev libelf-dev libexpat1-dev libgmp-dev '^liblz4-.*' \
'^liblzma.*' libmpc-dev libmpfr-dev libncurses-dev libncurses5 \
libncurses6 libsdl1.2-dev libssl-dev libswitch-perl libtinfo5 \
libtool libwxgtk3.0-gtk3-dev libxml-simple-perl libxml2 libxml2-utils \
locales lz4 lzip '^lzma.*' lzop \
maven nano ncftp patch patchelf \
pkg-config pngcrush pngquant protobuf-compiler python-is-python3 \
python2 python3 python3-dev python3-pip python3-protobuf \
re2c rsync schedtool squashfs-tools subversion \
systemd texinfo tzdata unzip w3m \
wget xsltproc zip zlib1g-dev
ln -sfv $(command -v python3) /usr/bin/python
# 获取官方 platform-tools 工具
curl -L -C - --retry 3 --retry-delay 5 --progress-bar \
-o "/root/workspace/platform-tools-latest-linux.zip" \
'https://dl.google.com/android/repository/platform-tools-latest-linux.zip'
unzip -o /root/workspace/platform-tools-latest-linux.zip -d /root/workspace/
# 删除压缩包节省空间
rm -frv /root/workspace/platform-tools-latest-linux.zip
# 获取镜像解包 ota extract-tools 工具
git clone https://github.com/LineageOS/android_prebuilts_extract-tools \
/root/workspace/android_prebuilts_extract-tools
# 创建repo环境
mkdir -pv /root/workspace/bin
curl -L -C - --retry 3 --retry-delay 5 --progress-bar \
-o "/root/workspace/bin/repo" \
'https://storage.googleapis.com/git-repo-downloads/repo'
chmod -v a+x /root/workspace/bin/repo
# 本地网络不行,需要修改 repo 源
#sed -i 's;https://gerrit.googlesource.com/git-repo;https://mirrors.tuna.tsinghua.edu.cn/git/git-repo;g' \
# /root/workspace/bin/repo
# 还原
#sed -i 's;https://mirrors.tuna.tsinghua.edu.cn/git/git-repo;https://gerrit.googlesource.com/git-repo;g' \
# /root/workspace/bin/repo
# 写入环境
cat << '469138946ba5fa' | tee /root/workspace/buildenv
# ===== Android build dedicated HOME =====
export ANDROID_BUILD_HOME=/root/workspace/.home
mkdir -p "${ANDROID_BUILD_HOME}"
export HOME="${ANDROID_BUILD_HOME}"
# 固定使用系统中可用的 locale
export LANG=en_US.utf8
export LANGUAGE=en_US:en
export LC_ALL=en_US.utf8
echo "Generating en_US.utf8 locale..."
locale-gen en_US.utf8
update-locale LANG=en_US.utf8
echo "Environment is set:"
locale
ln -fsv /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
dpkg-reconfigure -f noninteractive tzdata
# add Android SDK platform tools to path
if [ -d "/root/workspace/platform-tools" ] ; then
export PATH="/root/workspace/platform-tools:${PATH}"
fi
# add Android ota extract-tools tools to path
if [ -d "/root/workspace/android_prebuilts_extract-tools/linux-x86/bin" ] ; then
export PATH="/root/workspace/android_prebuilts_extract-tools/linux-x86/bin:${PATH}"
fi
# set PATH so it includes user's private bin if it exists
if [ -d "/root/workspace/bin" ] ; then
export PATH="/root/workspace/bin:${PATH}"
fi
# 强制 Java 使用 UTF-8 编码,防止 java 编译时读取字符产生错误
export JAVA_TOOL_OPTIONS="-Dfile.encoding=UTF-8"
# 创建临时存储链接
rm -fr ${HOME}/.cache
mkdir -pv ${HOME}/.cache
# 配置磁盘缓存相关配置增大缓存容量
export USE_CCACHE=1
export CCACHE_DIR=${HOME}/.cache
export CCACHE_EXEC=/usr/bin/ccache
ccache -M 50G
ccache -o compression=true
# 指定导出位置
#export OUT_DIR_COMMON_BASE=/root/workspace
# 配置 git 用户信息
git config --global user.email "469138946ba5fa@outlook.com"
git config --global user.name "469138946ba5fa"
# Switching the SSL backend to gnutls can resolve this issue.
#git config --global http.sslBackend gnutls
#git config --global --unset http.sslBackend
# Git 传输数据量较大,可能会导致超时或连接中断。你可以增加 Git 的缓冲区
#git config --global http.postBuffer 1048576000
#git config --global https.postBuffer 1048576000
#git config --global http.maxRequestBuffer 100M
# 网络连接不稳定,TLS 握手可能会失败。你可以增加超时时间
#git config --global http.lowSpeedLimit 1
#git config --global http.lowSpeedTime 600
# 有些服务器对 HTTP/2 兼容性较差,你可以强制 Git 使用 HTTP/1.1
#git config --global http.version HTTP/1.1
# 强制 Git 使用 HTTP/2
#git config --global http.version HTTP/2
# 跳过 ssl 证书校验
#git config --global http.sslVerify false
469138946ba5fa
# 加载自定义的 repo 和 adb 环境
source /root/workspace/buildenv
# 创建源码目录并获取(预计消耗 6h45m59.129s)
mkdir -pv /root/workspace/android-build/lineage-23.2
cd /root/workspace/android-build/lineage-23.2
repo init -u https://github.com/LineageOS/android.git -b lineage-23.2 --git-lfs --no-clone-bundle
# 本地网络不行,需要替换 AOSP 为 tsinghua 源
#sed -i 's;https://android.googlesource.com;https://mirrors.tuna.tsinghua.edu.cn/git/AOSP;g' \
# .repo/manifests/default.xml
# 还原
#sed -i 's;https://mirrors.tuna.tsinghua.edu.cn/git/AOSP;https://android.googlesource.com;g' \
# .repo/manifests/default.xml
# 通常情况下,你可以直接运行:首次同步代码
repo sync
# 如果过程中断,可以执行以下命令继续同步,这会清除锁定的文件
# 继续同步未完成的部分,而不会重新下载已经同步的内容。
find .repo -name "index.lock" -exec rm -f {} \;
repo sync -c --optimized-fetch --prune --force-sync
# 如果修改了本地文件无法同步可以,强制重置所有本地修改并清理,然后再执行 repo sync
repo forall -c 'git reset --hard HEAD && git clean -fdx' || true
repo sync -c --optimized-fetch --prune --force-sync -j$(nproc --all) --no-tags
# 可能会有如下警告,意味着这两个 git 仓库里有很多“松散对象(loose objects)”,git 还没来得及整理成 pack 文件,占用磁盘空间变大
# warning: Project "platform/prebuilts/clang/host/linux-x86" is accumulating unoptimized data.
# warning: Project "platform/prebuilts/misc" is accumulating unoptimized data.
# 跑一次官方推荐的垃圾回收命令即可
repo sync --auto-gc
# 完成同步后加载脚本环境
source build/envsetup.sh
# 尝试生成 Macmini M4 sdkmanager emulator 可以使用的虚拟机镜像 phone arm64 userdebug
breakfast sdk_phone_arm64 userdebug
# 编译镜像 croot brunch
croot
# 联合编译内核生成镜像
mka
# 查看得到 img 镜像
echo $OUT
ls -al $OUT
# 导出为 emulaotr 压缩包,包含一切虚拟所需文件。
mka emu_img_zip
# 现在查看 out/target/product/<arch> 目录,会发现一个名为 sdk-repo-linux-system-images.zip 的 ZIP 文件
ls -al $OUT/sdk-repo-linux-system-images.zip
传输 lineage 镜像包到 Macmikni M4 设备
容器内编译操作
假设局域网内
Macmini M4设备ip是192.168.255.253用户名是af5ab649831964远程ssh端口是22则有如下传输命令,路径就暂时将lineage镜像包传输到~/,输入密码后即可传输并在完成后删除源文件节省空间。
export IP_VALUE='192.168.255.253' PORT_VALUE='22' NAME_VALUE='af5ab649831964' ; ping -c4 $IP_VALUE ; nc -zv $IP_VALUE $PORT_VALUE && /bin/rsync -aPtuvz \
--remove-source-files \
--progress \
-e "ssh -o HostKeyAlgorithms=+ssh-rsa \
-o KexAlgorithms=+diffie-hellman-group1-sha1 \
-o PubkeyAcceptedAlgorithms=+ssh-rsa \
-o ServerAliveCountMax=10 \
-o ServerAliveInterval=30 \
-o StrictHostKeyChecking=no \
-o UserKnownHostsFile=/dev/null \
-p '${PORT_VALUE}' \
-t \
-v" \
$OUT'/sdk-repo-linux-system-images.zip' "${NAME_VALUE}@${IP_VALUE}"':~/'
搭建 android sdkmanager 环境
换到 Macmikni M4 设备,检查 ~/sdk-repo-linux-system-images.zip 是否存在,存在就说明已经传输成功,就可以继续了
# 检查镜像包路径是否存在
ls -al ~/sdk-repo-linux-system-images.zip
- 进入 android sdkmanager 官网点击 command-line-tools-only 下载对应架构的工具包,比如我的是 macmini m4 则下载 Download Android Command Line Tools for Mac
- 进入 https://github.com/adoptium 搜索最新版本
openjdk比如 openjdk 25 下载对应架构的包,比如我的是 macmini m4 则下载 OpenJDK25U-jdk_aarch64_mac_hotspot_25.0.1_8.tar.gz
sdkmanager 命令
接下来的环境搭建都是参考了 sdkmanager 命令 请享用
拼接解压
拼接创建 /cmdline-tools 和 /jdk 路径并解压 commandlinetools-mac-13114758_latest.zip 和 OpenJDK25U-jdk_aarch64_mac_hotspot_25.0.1_8.tar.gz
# 创建存放 sdkmanager 工具的路径结尾一定要拼接 `cmdline-tools` 比如
mkdir -pv "$HOME/Desktops/android_sdk/cmdline-tools"
# 创建存放 jdk 工具的路径这个没有特别的要求路径没有空格英文路径即可,比如
mkdir -pv "$HOME/Desktops/android_sdk/jdk"
# 解压 `commandlinetools-mac-13114758_latest.zip`
unzip -o commandlinetools-mac-13114758_latest.zip -d "$HOME/Desktops/android_sdk/cmdline-tools"
# 提取 `OpenJDK25U-jdk_aarch64_mac_hotspot_25.0.1_8.tar.gz` 根目录
OPENJDK_ROOT_PATH=$(tar tf OpenJDK25U-jdk_aarch64_mac_hotspot_25.0.1_8.tar.gz | awk -F'/' 'NF <= 2 { print }' | head -n 1)
echo $OPENJDK_ROOT_PATH
# 解压 `OpenJDK25U-jdk_aarch64_mac_hotspot_25.0.1_8.tar.gz`
tar zxvf OpenJDK25U-jdk_aarch64_mac_hotspot_25.0.1_8.tar.gz -C "$HOME/Desktops/android_sdk"
# 修改工具包 cmdline-tools 文件夹的名字
rm -frv "$HOME/Desktops/android_sdk/cmdline-tools/latest"
mv -fv "$HOME/Desktops/android_sdk/cmdline-tools/cmdline-tools" "$HOME/Desktops/android_sdk/cmdline-tools/latest"
# 修改替换 openjdk 路径后清空文件夹
# 需要注意的是 macos openjdk 根目录会有 Contents/Home
# linux 请将 jdk/Contents 改为 jdk
rm -frv "$HOME/Desktops/android_sdk/jdk/Contents"
# linux 请将 ${OPENJDK_ROOT_PATH}Contents 改为 ${OPENJDK_ROOT_PATH}
mv -fv "$HOME/Desktops/android_sdk/${OPENJDK_ROOT_PATH}Contents" "$HOME/Desktops/android_sdk/jdk"
rm -frv "$HOME/Desktops/android_sdk/${OPENJDK_ROOT_PATH}"
配置临时环境安装必备组件
补充配置临时环境变量,安装 platform-tools和emulator 两大件,后续也可以将这个环境写入到像是 .zshrc,.zprofile 和 .bashrc 之类的 shell 配置文件中一劳永逸
# 临时配置 `SDK root` 环境
# 定义基础目录
BASE_DIR="$HOME/Desktops"
ANDROID_SDK_DIR="$BASE_DIR/android_sdk"
JDK_DIR="$ANDROID_SDK_DIR/jdk"
# **加载 Android SDK 相关环境**
# https://developer.android.com/tools/sdkmanager
if [ -d "$ANDROID_SDK_DIR" ]; then
# android 模拟器路径 `.android`
export ANDROID_HOME="$ANDROID_SDK_DIR"
# sdk root 路径
export ANDROID_SDK_HOME="$ANDROID_HOME"
export ANDROID_SDK_ROOT="$ANDROID_SDK_HOME"
# openjdk 路径
# 需要注意的是 macos openjdk 根目录会有 Contents/Home
# linux 请将 $JDK_DIR/Contents/Home 改为 $JDK_DIR
JAVA_HOME="$JDK_DIR/Contents/Home"
export PATH="$ANDROID_SDK_HOME/cmdline-tools/latest/bin:$ANDROID_SDK_HOME/platform-tools:$ANDROID_SDK_HOME/emulator:$JAVA_HOME/bin:$PATH"
echo "已配置 Android SDK 环境"
else
echo "未检测到 Android SDK,跳过所有相关工具的配置"
fi
# 测试 `java` 命令
java --version
# 测试 `sdkmanager` 命令
sdkmanager --version
# 检查目前最新版的 `platform-tools`和`emulator` 三大件
sdkmanager --list | grep -EIi 'platform-tools' | tail -n 1
sdkmanager --list | grep -EIi 'emulator' | grep -v 'extras' | tail -n 1
# 将信息存为变量用作的下载
PLATFORM_TOOLS=$(sdkmanager --list | grep -EIi 'platform-tools' | tail -n 1 | awk 'NR=1 {print $1}')
EMULATER=$(sdkmanager --list | grep -EIi 'emulator' | grep -v 'extras' | tail -n 1 | awk 'NR=1 {print $1}')
# 在 Linux / macOS / Git Bash 上自动化接受许可证
yes | sdkmanager --licenses
# 批量安装所需包 `platform-tools`和`emulator` 两大件
sdkmanager --install $PLATFORM_TOOLS $EMULATER
# 检查两大件已经展示在列表
sdkmanager --list_installed
# 测试 `adb`, `fastboot`, `avdmanager`, `emulator` 命令
adb --version
fastboot --version
emulator -version
avdmanager --help
sdkmanager 关联 lineage 镜像并测试运行 lineage 虚拟镜像
现在将 sdk-repo-linux-system-images.zip 解压到指定位置并关联到 sdkmanager
# 创建标准的 API 目录和自定义 tag 目录
mkdir -pv $HOME/Desktops/android_sdk/system-images/android-36.1/lineage
# 进入目录
cd $HOME/Desktops/android_sdk/system-images/android-36.1/lineage
# 将 arm64-v8a 镜像解压到新目录中并删除压缩包节省空间
unzip -o $HOME/sdk-repo-linux-system-images.zip -d ./ && rm -fv $HOME/sdk-repo-linux-system-images.zip
查看镜像信息
cat arm64-v8a/source.properties
可以得到如下信息
Pkg.Desc=LineageOS System Image API 16 arm64-v8a
Pkg.Revision=1
AndroidVersion.ApiLevel=36.1
SystemImage.Abi=arm64-v8a
SystemImage.TagId=lineage
SystemImage.TagDisplay=LineageOS
根据镜像信息写入 sdkmanager 可以识别的包配置 xml 用来关联
nano arm64-v8a/package.xml
编辑内容如下
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns2:repository xmlns:ns2="http://schemas.android.com/repository/android/common/02"
xmlns:ns3="http://schemas.android.com/repository/android/common/01"
xmlns:ns4="http://schemas.android.com/repository/android/generic/01"
xmlns:ns5="http://schemas.android.com/repository/android/generic/02"
xmlns:ns6="http://schemas.android.com/sdk/android/repo/addon2/01"
xmlns:ns7="http://schemas.android.com/sdk/android/repo/addon2/02"
xmlns:ns8="http://schemas.android.com/sdk/android/repo/addon2/03"
xmlns:ns9="http://schemas.android.com/sdk/android/repo/repository2/01"
xmlns:ns10="http://schemas.android.com/sdk/android/repo/repository2/02"
xmlns:ns11="http://schemas.android.com/sdk/android/repo/repository2/03"
xmlns:ns12="http://schemas.android.com/sdk/android/repo/sys-img2/04"
xmlns:ns13="http://schemas.android.com/sdk/android/repo/sys-img2/03"
xmlns:ns14="http://schemas.android.com/sdk/android/repo/sys-img2/02"
xmlns:ns15="http://schemas.android.com/sdk/android/repo/sys-img2/01">
<license id="android-sdk-arm-dbt-license" type="text">It's Ok!</license>
<!-- path 这里的格式是 目录1;目录2;目录3;目录4 -->
<localPackage path="system-images;android-36.1;lineage;arm64-v8a" obsolete="false"><type-details
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:type="ns12:sysImgDetailsType"><api-level>36.1</api-level><extension-level>17</extension-level><base-extension>true</base-extension>
<tag>
<id>lineage</id>
<display>Lineage APIs</display>
</tag>
<vendor>
<id>lineage</id>
<display>Lineage Inc.</display>
</vendor>
<abi>arm64-v8a</abi>
<abis>arm64-v8a</abis>
</type-details>
<revision>
<major>7</major>
</revision><display-name>Lineage APIs ARM 64 v8a System Image</display-name><uses-license
ref="android-sdk-arm-dbt-license" />
<dependencies>
<dependency path="emulator"><min-revision>
<major>35</major>
<minor>4</minor>
<micro>9</micro>
</min-revision></dependency>
</dependencies>
</localPackage>
</ns2:repository>
测试 sdkmanager 是否识别?
sdkmanager --list
可以看到如下内容有 system-images;android-36.1;lineage;arm64-v8a 说明关联成功
Warning: Failed to download any source lists!
Warning: IO exception while downloading manifest
Warning: IO exception while downloading manifest
Warning: Still waiting for package manifests to be fetched remotely.
[=======================================] 100% Computing updates...
Installed packages:
Path | Version | Description | Location
------- | ------- | ------- | -------
build-tools;36.1.0-rc1 | 36.1.0 rc1 | Android SDK Build-Tools 36.1-rc1 | build-tools/36.1.0-rc1
emulator | 36.5.10 | Android Emulator | emulator
platform-tools | 37.0.0 | Android SDK Platform-Tools | platform-tools
system-images;android-36.1;lineage;arm64-v8a | 7 | Lineage APIs ARM 64 v8a System Image | system-images/android-36.1/lineage/arm64-v8a
测试运行 lineage 虚拟镜像
查找 pixel 设备将其存储为又臭又长的变量用作安装
DEVIDE_NAME=$(avdmanager list device | \
grep -E 'pixel' | \
sed -E 's/.*pixel_([0-9]{1,}).*/\1&/' | \
sort -k1,1nr -k2,2r | \
head -n 1 | \
awk -F' or "' '{ print $2 }' | \
tr -d '"' | \
awk '{ print $1 }')
echo $DEVIDE_NAME
给 android 模拟器起个名字,比如 pixel_9a_Lineage23.2_aarch64
AVD_NAME=${DEVIDE_NAME}_Lineage23.2_aarch64
镜像包就是 sdkmanager --list 获取的自定义 lineage 包
SYSTEM_IMAGES_VERSION='system-images;android-36.1;lineage;arm64-v8a'
先删除重名虚拟机如果之前创建过的话,以防万一,然后创建 lineageos 虚拟机
# 删除重名虚拟机
avdmanager delete avd --name $AVD_NAME
# 创建 lineageos 虚拟机
echo no | avdmanager create avd \
--force \
--device "$DEVIDE_NAME" \
--name "$AVD_NAME" \
--package "$SYSTEM_IMAGES_VERSION"
# 由于我配置了 ANDROID_SDK_HOME 所以配置文件在 $ANDROID_SDK_HOME/.android 中
# 所以假设我们已经确定了 AVD 的配置文件的路径
CONFIG_FILE="$ANDROID_SDK_HOME/.android/avd/$AVD_NAME.avd/config.ini"
echo $CONFIG_FILE
# 校验 CONFIG_FILE 是否存在
if [ -z "$CONFIG_FILE" ] || [ ! -f "$CONFIG_FILE" ]; then
echo "错误:CONFIG_FILE 未定义或文件不存在!"
fi
# =========================================================================
# 函数:检查并设置配置值
# 参数1: 配置键 (例如: hw.lcd.height)
# 参数2: 配置值 (例如: 3120)
# 依赖: CONFIG_FILE 变量必须指向 AVD 的 config.ini 文件
# =========================================================================
set_config_value() {
local key="$1"
local value="$2"
local new_line="${key} = ${value}"
# 检查 key 是否已存在
if grep -q "^${key}" "$CONFIG_FILE"; then
# --- 跨平台兼容性设置 ---
# 检测系统,如果是 macOS (Darwin),则需要 -i '',否则为 -i
if [ "$(uname)" = "Darwin" ]; then
# macOS BSD sed
# 用安全的方式替换整行
sed -i '' "s;^${key}.*;${new_line};" "$CONFIG_FILE"
else
# GNU sed
# 用安全的方式替换整行
sed -i "s;^${key}.*;${new_line};" "$CONFIG_FILE"
fi
if [ $? -eq 0 ]; then
echo "更新配置:$new_line"
else
echo "错误:更新 $key 失败!" >&2
fi
else
# 不存在则追加
echo "$new_line" >> "$CONFIG_FILE"
echo "追加配置:$new_line"
fi
}
# --- 执行配置修改 (与之前相同) ---
echo "开始修改 AVD 配置文件:$CONFIG_FILE"
# 禁用或检查蓝牙(有时蓝牙的虚拟驱动可能影响网络)
set_config_value "hw.bluetooth" "no"
# 检查 Wi-Fi 是否启用(默认通常是 yes)
set_config_value "hw.wifi" "yes"
# 屏幕分辨率
set_config_value "hw.lcd.height" "3120"
set_config_value "hw.lcd.width" "1440"
# 屏幕 DPI
set_config_value "hw.lcd.density" "560"
# 禁用模拟器的边框
set_config_value "showDeviceFrame" "no"
# 其他显示相关设置
set_config_value "hw.lcd.depth" "16"
set_config_value "hw.lcd.vsync" "60"
# 修改内存,通常需要使用后缀 `M` (MB) 或 `G` (GB)不一定生效
# 同时,你可能需要调整 Dalvik/ART 堆大小,你通常需要使用后缀 `M` (MB) 或 `G` (GB)
# 如果你的 RAM 很大,可以相应增加堆大小
# 内存与堆大小关系 2G -> 256M, 4G -> 512M 以此类推
# 2G=2*1024M -> 256M=2*128M
set_config_value "hw.ramSize" "$((4*1024))M"
set_config_value "vm.heapSize" "$((4*128))M"
# 开启音频输出/入
# 确保这两行存在且设置为 yes
set_config_value "hw.audioInput" "yes"
set_config_value "hw.audioOutput" "yes"
# 开启双摄像头
# 如果你希望测试 ARCore 或只是一个虚拟场景:
# hw.camera.back = virtualscene
# hw.camera.front = none
# 配置双摄像头都使用宿主机摄像头 (Webcam0)
set_config_value "hw.camera.back" "webcam0"
set_config_value "hw.camera.front" "webcam0"
# 修改存储器大小
# 32G=32*1024^3B
set_config_value "disk.dataPartition.size" "$((32*1024*1024*1024))"
# 修改存储器路径
set_config_value "disk.dataPartition.path" "$ANDROID_SDK_HOME/.android/avd/$AVD_NAME.avd"
# 让新存储器生效,等待 30s
# 30s 后终止
emulator -avd "$AVD_NAME" \
-wipe-data \
-no-snapshot-load \
-writable-system \
-no-boot-anim \
-no-window \
-no-audio 2>&1 &
sleep 30 && pkill -f 'avd '$AVD_NAME
echo "配置修改完成。请检查"
CONFIG_INFO='
hw.lcd.height
hw.lcd.width
hw.lcd.density
showDeviceFrame
hw.lcd.depth
hw.lcd.vsync
hw.ramSize
vm.heapSize
disk.dataPartition.size
disk.dataPartition.path
hw.audioInput
hw.audioOutput
hw.camera.back
hw.camera.front
hw.bluetooth
hw.wifi
'
echo "--- AVD配置检测报告 ---"
# 关键:通过管道将变量内容逐行传递给 read 命令
echo "$CONFIG_INFO" | grep -v '^[[:space:]]*$' | while read config
do
# 确保 config 非空(排除变量定义开头或结尾的空行)
if [ -n "$config" ]; then
if grep -Eiq "^[[:space:]]*${config}[[:space:]]*=" "$CONFIG_FILE"; then
# 查找并打印参数值
echo "$config: $(grep -E "^[[:space:]]*${config}[[:space:]]*=" "$CONFIG_FILE" | head -n 1 | cut -d '=' -f 2- | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
else
echo "$config: 未找到"
fi
fi
done
echo "--- 报告结束 ---"
最终以后想要重启运行的命令
# 查看虚拟机列表
emulator -list-avds
# 假设 lineageos 虚拟机名字是 pixel_9a_Lineage23.2_aarch64
export AVD_NAME='pixel_9a_Lineage23.2_aarch64'
# 终止并删除锁文件,然后启动虚拟机
pkill -9 -f "emulator.*$AVD_NAME" || true ; rm -rf $HOME/Desktops/android_sdk/.android/avd/${AVD_NAME}.avd/*.lock ; sleep 10 ; emulator -avd "$AVD_NAME" \
-memory $((4*1024)) \
-no-snapshot-load \
-writable-system \
-port 5554 \
-qemu -netdev user,id=net0,hostfwd=tcp::5555-:5555 \
-device virtio-net-device,netdev=net0 &
disown ; sleep 10 ; adb kill-server ; adb devices ; adb -s emulator-5554 tcpip 5555
最终效果
参考&感谢
wiki.lineageos.org emulator
OnePlus 7 Pro LineageOS 23.2 Android 16 构建与内核 Docker 支持并在 Termux 上实现容器运行全流程指南
sdkmanager avdmanager emulator 创建运行 avd android 模拟器
kernel-a16-raphael-antigravity-kernel-with-docker-support-for-raphael.4769126
Mamba User Guide — documentation
[Root] Termux:以原生效能在Android手機上跑Docker (紅米Note 5)
docker-on-android
Community – LineageOS
Build for guacamole | LineageOS Wiki
Extracting proprietary blobs from LineageOS zip files | LineageOS Wiki
Repo command reference | Android Open Source Project
tsinghua git-repo 源
lineage os
tsinghua AOSP 源
crdroid guacamole 固件镜像
github LineageOS/android_prebuilts_extract-tools
github TheMuppets/proprietary_vendor_oneplus
github moby/moby check-config.sh
docker use
github termux/termux-packages
Termux:boot app
github docker/cli
github moby/moby
github krallin/tini
github containerd/containerd
crdroid 固件刷机脚本
docker/buildx/issues/1834
docker/buildx/issues/136
moby/buildkit/blob/master/docs/buildkitd.toml.md
daxiaamu OP7P
android sdkmanager
sdkmanager
avdmanager
openjdk
Comments
Post a Comment