通过 x86_64 主机为 Macmini M4 sdkmanager emulator 编译 LineageOS 23.2 Android 16 镜像并识别运行

通过 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 主机配置环境

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 设备 ip192.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
  1. 进入 android sdkmanager 官网点击 command-line-tools-only 下载对应架构的工具包,比如我的是 macmini m4 则下载 Download Android Command Line Tools for Mac
  2. 进入 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.zipOpenJDK25U-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-toolsemulator 两大件,后续也可以将这个环境写入到像是 .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

最终效果
0

参考&感谢

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