简介对比
Redroid (Remote Android) 是一个 GPU 加速的 AIC(云端 Android)解决方案。你可以在 Linux 主机上(如 Docker、Podman、K8s 等)启动多个实例。Redroid 支持 arm64 和 amd64 架构,适用于云游戏、虚拟手机、自动化测试等场景。
系统支持
目前支持:
- 安卓 15 (
redroid/redroid:15.0.0-latest) - 仅限 Android 15 64 位(
redroid/redroid:15.0.0_64only-latest) - Android 14(
redroid/redroid:14.0.0-latest) - 仅限 Android 14 64 位(
redroid/redroid:14.0.0_64only-latest) - Android 13(
redroid/redroid:13.0.0-latest) - 仅限 Android 13 64 位(
redroid/redroid:13.0.0_64only-latest) - Android 12(
redroid/redroid:12.0.0-latest) - 仅限 Android 12 64 位(
redroid/redroid:12.0.0_64only-latest) - Android 11(
redroid/redroid:11.0.0-latest) - Android 10(
redroid/redroid:10.0.0-latest) - Android 9(
redroid/redroid:9.0.0-latest) - Android 8.1(
redroid/redroid:8.1.0-latest)
实际运行效果
工具安装
安装GMS提供谷歌app下载并实现设备认证
GMS服务简介
Google流动服务(GMS)(英文:Google Mobile Services)是Google的一项服务,让用户利用流动电话或其他流动装置使用Google搜寻、Google地图、Gmail、YouTube等Google产品。
目前,Google流动服务不再允许用户自行安装,自行安装可能无法通过Play兼容性测试,也无法下载需要DRM的软件。
安装GMS
创建文件 gapps_build.sh, 填写以下内容. 执行命令 ./gapps_build.sh <当前路径>
#!/bin/bash
# 下载链接和对应的 MD5 值
declare -A dl_links=(
["x86_64"]="https://cfhcable.dl.sourceforge.net/project/opengapps/x86_64/20220503/open_gapps-x86_64-10.0-pico-20220503.zip 5fb186bfb7bed8925290f79247bec4cf"
["x86"]="https://udomain.dl.sourceforge.net/project/opengapps/x86/20220122/open_gapps-x86-10.0-pico-20220122.zip 9e39e45584b7ade4529e6be654af7b81"
["arm64-v8a"]="https://liquidtelecom.dl.sourceforge.net/project/opengapps/arm64/20220122/open_gapps-arm64-10.0-pico-20220122.zip 8dfa6e76aeb2d1d5aed40b058e8a852c"
["armeabi-v7a"]="https://nav.dl.sourceforge.net/project/opengapps/arm/20220122/open_gapps-arm-10.0-pico-20220122.zip a48ccbd25eb0a3c5e30f5db5435f5536"
)
# 获取主机架构
arch=$(uname -m)
case $arch in
x86_64)
key="x86_64"
;;
x86)
key="x86"
;;
aarch64)
key="arm64-v8a"
;;
arm*)
key="armeabi-v7a"
;;
*)
echo "Unsupported architecture: $arch"
exit 1
;;
esac
# 解析下载链接和 MD5 值
dl_link=$(echo ${dl_links[$key]} | awk '{print $1}')
act_md5=$(echo ${dl_links[$key]} | awk '{print $2}')
# 下载位置和文件名
download_loc=$1
dl_file_name="$download_loc/open_gapps.zip"
copy_dir="./gapps"
extract_to="/tmp/ogapps/extract"
non_apks=("defaultetc-common.tar.lz" "defaultframework-common.tar.lz" "googlepixelconfig-common.tar.lz")
skip=("setupwizarddefault-x86_64.tar.lz" "setupwizardtablet-x86_64.tar.lz")
# 创建下载目录
mkdir -p "$download_loc"
# 下载 OpenGapps
echo -e "\033[32mDownloading OpenGapps now .....\033[0m"
# wget -O "$dl_file_name" "$dl_link"
# 验证 MD5 值
if [[ "$(md5sum "$dl_file_name" | awk '{print $1}')" != "$act_md5" ]]; then
echo "MD5 mismatch!"
exit 1
fi
# 创建提取目录
rm -rf "$copy_dir"
mkdir -p "$copy_dir"
mkdir -p "$extract_to/appunpack"
# 解压下载的文件
echo "Extracting OpenGapps ..."
unzip -d "$extract_to" "$dl_file_name"
# 处理提取的文件
for lz_file in "$extract_to/Core"/*.tar.lz; do
# 清空 appunpack 目录
rm -rf "$extract_to/appunpack/*"
# 检查是否跳过此文件
if [[ ! " ${skip[@]} " =~ " $(basename "$lz_file") " ]]; then
if [[ ! " ${non_apks[@]} " =~ " $(basename "$lz_file") " ]]; then
echo "Processing app package : $lz_file"
tar --lzip -xvf "$lz_file" -C "$extract_to/appunpack"
app_name=$(ls "$extract_to/appunpack" | head -n 1)
xx_dpi=$(ls "$extract_to/appunpack/$app_name" | head -n 1)
app_priv=$(ls "$extract_to/appunpack/$app_name/nodpi" | head -n 1)
app_src_dir="$extract_to/appunpack/$app_name/$xx_dpi/$app_priv"
mkdir -p "$copy_dir/system/priv-app"
for app in "$app_src_dir"/*; do
cp -r "$app" "$copy_dir/system/priv-app/$(basename "$app")"
done
else
echo "Processing extra package : $lz_file"
tar --lzip -xvf "$lz_file" -C "$extract_to/appunpack"
app_name=$(ls "$extract_to/appunpack" | head -n 1)
common_content_dirs=$(ls "$extract_to/appunpack/$app_name/common")
mkdir -p "$copy_dir/system"
for ccdir in $common_content_dirs; do
cp -r "$extract_to/appunpack/$app_name/common/$ccdir" "$copy_dir/system/$ccdir"
done
fi
fi
done
打包Docker
创建Dockerfile文件
FROM redroid/redroid:11.0.0 -latest
COPY gapps /
运行命令 docker build --build-arg userid=(id -g) --build-arg username=$(id -un) -t redroid-builder-gapps .
谷歌设备认证
在安装完GMS后可能会出现 PlayProtect "Device Not Certified" 的提示
通过adb连接设备并获取对应的android id进行手动注册
执行
adb root
adb shell 'sqlite3 /data/data/com.google.android.gsf/databases/gservices.db \
"select * from main where name = "android_id";"
访问https ://www.google.com/android/uncertified/ 填入android id进行注册。重启设备后,就可以下载google store的app
安装面具Magisk实现动态修改设备信息
Magisk简介
Magisk通过挂载一个和系统文件完全隔离的空间,实现数据的动态修改和恢复。实际是通过启动时在 boot 中创建钩子,把 /data/magisk.img 挂载到 /magisk,构建出一个在 system 基础上能够自定义替换,增加以及删除的文件系统,所有操作都在启动的时候完成,实际上并没有对 /system 分区进行修改(即 systemless 接口,以不触动 /system 的方式修改 /system)。
安装Magisk
创建文件 magisk_build.sh, 填写以下内容. 执行命令 ./magisk_build.sh <当前路径>
安装Magisk
创建文件 magisk_build.sh, 填写以下内容. 执行命令 ./magisk_build.sh <当前路径>
#!/bin/bash
# Set download location and variables
download_loc=$1
echo "download_loc", $download_loc
dl_link="https://web.archive.org/web/20230718224206if_/https://objects.githubusercontent.com/github-production-release-asset-2e65be/514574759/50ec2f91-174b-4918-8587-04e847458bfd?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIWNJYAX4CSVEH53A%2F20230718%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20230718T224206Z&X-Amz-Expires=300&X-Amz-Signature=ee54e872b4d3c1388601941e85b2fcf84d5e06968618271ea2f5e3ea5947d4e1&X-Amz-SignedHeaders=host&actor_id=0&key_id=0&repo_id=514574759&response-content-disposition=attachment%3B%20filename%3Dapp-debug.apk&response-content-type=application%2Fvnd.android.package-archive"
dl_file_name="$download_loc/magisk.apk"
act_md5="ec98dcee84a47785dc551eb7c465b25f"
extract_to="/tmp/magisk_unpack"
copy_dir="./magisk"
magisk_dir="$copy_dir/system/etc/init/magisk"
machine=$(uname -m)
# Create directories
rm -rf "$copy_dir"
mkdir -p "$magisk_dir"
mkdir -p "$copy_dir/sbin"
# Download Magisk
echo -e "\033[32mDownloading latest Magisk-Delta now .....\033[0m"
wget "$dl_link" -O "$dl_file_name"
# Verify MD5
if [ "$(md5sum "$dl_file_name" | awk '{ print $1 }')" != "$act_md5" ]; then
echo "MD5 checksum does not match!"
exit 1
fi
unzip -d $extract_to $dl_file_name
# Copy Magisk libraries
echo -e "\033[32mCopying magisk libs now ...\033[0m"
lib_dir="$extract_to/lib/$machine"
for filename in "$lib_dir"/*.so; do
if [[ -f "$filename" ]]; then
base_name=$(basename "$filename" .so)
n_path="$magisk_dir/$base_name"
cp "$filename" "$n_path"
chmod +x "$n_path"
fi
done
# Copy Magisk APK
cp "$dl_file_name" "$magisk_dir/magisk.apk"
# Prepare bootanim.rc
bootanim_path="$copy_dir/system/etc/init/bootanim.rc"
gz_filename="$bootanim_path.gz"
original_bootanim="
service bootanim /system/bin/bootanimation
class core animation
user graphics
group graphics audio
disabled
oneshot
ioprio rt 0
task_profiles MaxPerformance
"
bootanim_component="
on post-fs-data
start logd
exec u:r:su:s0 root root -- /system/etc/init/magisk/magisk${machine} --auto-selinux --setup-sbin /system/etc/init/magisk
exec u:r:su:s0 root root -- /system/etc/init/magisk/magiskpolicy --live --magisk \"allow * magisk_file lnk_file *\"
mkdir /sbin/.magisk 700
mkdir /sbin/.magisk/mirror 700
mkdir /sbin/.magisk/block 700
copy /system/etc/init/magisk/config /sbin/.magisk/config
rm /dev/.magisk_unblock
exec u:r:su:s0 root root -- /sbin/magisk --auto-selinux --post-fs-data
wait /dev/.magisk_unblock 40
rm /dev/.magisk_unblock
on zygote-start
exec u:r:su:s0 root root -- /sbin/magisk --auto-selinux --service
on property:sys.boot_completed=1
mkdir /data/adb/magisk 755
exec u:r:su:s0 root root -- /sbin/magisk --auto-selinux --boot-complete
exec -- /system/bin/sh -c \"if [ ! -e /data/data/io.github.huskydg.magisk ] ; then pm install /system/etc/init/magisk/magisk.apk ; fi\"
on property:init.svc.zygote=restarting
exec u:r:su:s0 root root -- /sbin/magisk --auto-selinux --zygote-restart
on property:init.svc.zygote=stopped
exec u:r:su:s0 root root -- /sbin/magisk --auto-selinux --zygote-restart
"
# Create gzipped bootanim.rc
echo -n "$original_bootanim" | gzip > "$gz_filename"
echo -e "$original_bootanim$bootanim_component" > "$bootanim_path"
# Set permissions
echo "0o644" $bootanim_path
chmod 644 "$bootanim_path"
打包Docker
创建Dockerfile文件
FROM redroid/redroid:11.0.0-latest
COPY magisk /
运行命令 docker build --build-arg userid=$(id -u) --build-arg groupid=$(id -g) --build-arg username=$(id -un) -t redroid-builder-magisk .
动态修改设备信息
在使用redroid可以通过以下两种方式进行设备信息修改。这两者实际是通过改写build.prop文件内容进行设备信息修改
Magisk模块
首选需要安装MagiskManager app, 安装方式参考本次文章。然后安装下列任意模块即可覆盖机型型号,设备参数等字段信息
magisk.suchenqaq.club/query.php?f…
你也可以自定义编写 Magisk 模块进行安装,主要是覆盖相应的系统属性。
Redroid命令行指定
可以不依赖MagiskManager和Magisk模块,只通过命令行参数指定覆盖build.prop对应的系统属性。可修改属性字段的参考
# begin common build properties
# autogenerated by build/make/tools/buildinfo_common.sh
ro.system.build.date=Mon May 27 14:38:53 UTC 2024
ro.system.build.date.utc=1716820733
ro.system.build.fingerprint=redroid/redroid_x86_64/redroid_x86_64:11/RD2A.211001.002/frank05271438:userdebug/test-keys
ro.system.build.id=RD2A.211001.002
ro.system.build.tags=test-keys
ro.system.build.type=userdebug
ro.system.build.version.incremental=eng.frank.20240527.144006
ro.system.build.version.release=11
ro.system.build.version.release_or_codename=11
ro.system.build.version.sdk=30
ro.product.system.brand=redroid
ro.product.system.device=redroid_x86_64
ro.product.system.manufacturer=redroid
ro.product.system.model=redroid11_x86_64
ro.product.system.name=redroid_x86_64
# end common build properties
# begin build properties
# autogenerated by buildinfo.sh
ro.build.id=RD2A.211001.002
ro.build.display.id=redroid_x86_64-userdebug 11 RD2A.211001.002 eng.frank.20240527.144006 test-keys
ro.build.version.incremental=eng.frank.20240527.144006
ro.build.version.sdk=30
ro.build.version.preview_sdk=0
ro.build.version.preview_sdk_fingerprint=REL
ro.build.version.codename=REL
ro.build.version.all_codenames=REL
ro.build.version.release=11
ro.build.version.release_or_codename=11
ro.build.version.security_patch=2021-10-01
ro.build.version.base_os=
ro.build.version.min_supported_target_sdk=23
ro.build.date=Mon May 27 14:38:53 UTC 2024
ro.build.date.utc=1716820733
ro.build.type=userdebug
ro.build.user=frank
ro.build.host=build0
ro.build.tags=test-keys
ro.build.flavor=redroid_x86_64-userdebug
# ro.product.cpu.abi and ro.product.cpu.abi2 are obsolete,
# use ro.product.cpu.abilist instead.
ro.product.cpu.abi=x86_64
ro.product.cpu.abilist=x86_64,x86,arm64-v8a,armeabi-v7a,armeabi
ro.product.cpu.abilist32=x86,armeabi-v7a,armeabi
ro.product.cpu.abilist64=x86_64,arm64-v8a
ro.product.locale=en-US
ro.wifi.channels=
# ro.build.product is obsolete; use ro.product.device
ro.build.product=redroid_x86_64
# Do not try to parse description or thumbprint
ro.build.description=redroid_x86_64-userdebug 11 RD2A.211001.002 eng.frank.20240527.144006 test-keys
# end build properties
#
# ADDITIONAL_BUILD_PROPERTIES
#
ro.treble.enabled=true
persist.debug.dalvik.vm.core_platform_api_policy=just-warn
dalvik.vm.lockprof.threshold=500
net.bt.name=Android
启动命令
docker run -itd --rm --privileged \
-v ~/android/data/data5655:/data \
-p 5655:5555 \
redroid/redroid:11.0.0-latest \
ro.product.cpu.abilist=x86_64,arm64-v8a,x86,armeabi-v7a,armeabi \
ro.product.cpu.abilist64=x86_64,arm64-v8a \
ro.product.cpu.abilist32=x86,armeabi-v7a,armeabi \
ro.dalvik.vm.isa.arm=x86 \
ro.dalvik.vm.isa.arm64=x86_64
安装ndk实现arm64指令翻译
libndk_translation简介
libndk_translation是用于在x86机器运行arm架构应用的指令翻译模块,可以将app中的一些arm指令翻译为对应x86指令。但存在部分模块翻译失败,或者冲突的情况。
安装NDK
创建文件 ndk_build.sh, 填写以下内容. 执行命令 ./ndk_build.sh <当前路径>
# 设置下载位置、提取位置和文件名
download_loc=$1
copy_dir="./ndk"
dl_link="https://github.com/supremegamers/vendor_google_proprietary_ndk_translation-prebuilt/archive/181d9290a69309511185c4417ba3d890b3caaaa8.zip"
dl_file_name="${download_loc}/libndktranslation.zip"
extract_to="/tmp/libndkunpack"
act_md5="0beff55f312492f24d539569d84f5bfb"
# 下载 libndk
echo -e "\033[32mDownloading libndk now .....\033[0m"
wget -O "${dl_file_name}" "${dl_link}"
# 创建提取目录
mkdir -p "${extract_to}"
# 解压下载的文件
unzip "${dl_file_name}" -d "${extract_to}"
# 复制 libndk 库文件
if [ -d "${copy_dir}" ]; then
rm -rf "${copy_dir}"
fi
chmod +x "${extract_to}"/* -R
echo -e "\033[32mCopying libndk library files ...\033[0m"
mkdir -p "${copy_dir}/system"
cp -r "${extract_to}/vendor_google_proprietary_ndk_translation-prebuilt-181d9290a69309511185c4417ba3d890b3caaaa8/prebuilts" "${copy_dir}/system"
# 设置 init 文件的权限
init_path="${copy_dir}/system/etc/init/ndk_translation.rc"
chmod 644 "${init_path}"
打包Docker
创建Dockerfile文件
COPY ndk /
运行命令 docker build --build-arg userid=$(id -u) --build-arg groupid=$(id -g) --build-arg username=$(id -un) -t redroid-builder-ndk .
常见问题
- libndk_translation 存在部分指令翻译失败,例如flutter应用可能在x86架构上的机器运行失败. 类似 Abort message: 'vendor/unbundled_google/libs/ndk_translation/runtime/host_call_frame_arm.cc:41: CHECK failed: 2927458984 == 2927460320'。 这种情况换aarch64的机器重新部署 可以参考章节
qemu安装aarch64系统运行arm - 在翻译指令过程中,内存会使用增大,因此后续在运行redroid应用时预留多一些内存
编译redroid并打包docker镜像
环境准备
以下环境安装二选一执行即可
环境镜像
Dockerfile如下,已经包含依赖项:Android SDK、JDK 8、Python 3.8 和 Ubuntu 20.04。
以及所需的依赖包:
- repo
- 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
FROM ubuntu:20.04
ARG userid=1000
ARG groupid=1000
ARG username=crawler
# 使用这些参数
RUN echo "User ID: ${userid}, Group ID: ${groupid}, Username: ${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 \
&& echo "install wget" \
&& apt install wget
RUN yes|apt-get install libwxgtk3.0-gtk3-0v5
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
RUN echo "创建下载目录/home/$username/download" \
&& mkdir -p /home/$username/download \
&& wget https://dl.google.com/android/repository/platform-tools-latest-linux.zip -O /home/$username/download/platform-tools-latest-linux.zip \
&& unzip /home/$username/download/platform-tools-latest-linux.zip -d /home/$username \
&& echo "create project dir" \
&& mkdir -p /home/$username/android/lineage
ENV HOME=/home/$username \
USER=$username \
USE_CCACHE=1 \
CCACHE_EXEC=/usr/bin/ccache \
PATH=/src/.repo/repo:/src/prebuilts/jdk/jdk8/linux-x86/bin/:/home/$username/platform-tools:$PATH
# jACK IS THE CURRENTLY USED jAVA TOOLCHAIN FOR BUILDING lINEAGEos 14.1 AND 15.1. iT IS KNOWN TO RUN OUT OF MEMORY OFTEN IF NOT CONFIGURED CORRRT Jack_vm_args="-dFILE.ENCODING=utf-8 -xx:+tIEREDcOMPILATION -xMX4g"
# ENV ANDROID_JACK_VM_ARGS="-Dfile.encoding=UTF-8 -XX:+TieredCompilation -Xmx4G"
ENTRYPOINT chroot --userspec=$(cat /root/username):$(cat /root/username) / /bin/bash -i
执行docker build -t redroid-build-base . 打包编译环境。或者直接拉取内部环境镜像到本地执行redroid编译
本地环境
依赖环境: 内存>16G, CPU > 8 core, 系统Ubuntu20.04
其他依赖参考上述Dockerfile 执行apt-get install安装相应的依赖包即可
Redroid自编译
编译步骤参考下述链接,直接跳到第5步开始执行
qemu安装aarch64系统运行arm镜像
qemu简介
hosomikai317.blogspot.com/2018/07/qem…
安装qemu
cloud.tencent.com/developer/a…
参考启动命令
& "F:\Program Files\qemu\qemu-system-aarch64.exe" -m 8192 -cpu cortex-a72 -smp 8,sockets=4,cores=2 -M virt -bios "F:\VM\ARM\ubuntu\QEMU_EFI.fd" -device VGA -device nec-usb-xhci -device usb-mouse -device usb-kbd -drive if=none,file="F:\VM\ARM\ubuntu\ubuntu.qcow2",id=hd0 -device virtio-blk-device,drive=hd0 -drive if=none,file=,id=cdrom,media=cdrom -device virtio-scsi-device -device scsi-cd,drive=cdrom -net nic -net user,hostfwd=tcp:127.0.0.1:2222-:22,hostfwd=tcp:127.0.0.1:5555-:5555
常见问题
由于qemu是通过动态二进制模拟CPU执行,从而模拟出相应的IO硬件设备(如网卡,磁盘等). 在模拟执行过程中需要时间翻译指令,因此当遇到需要图像渲染的情况就会异常缓慢
若需要稳定运行redroid的arm镜像,可以购买arm服务器或者裸机开发板,树莓派,香橙派(www.cnblogs.com/kanadebliss…)等
将第三方应用预设为系统应用
系统应用和普通应用的区别
安装位置
- 系统应用:存放在设备的
/system分区下的app或priv-app目录中。这些应用在出厂时由设备制造商或操作系统开发者预装在系统中。同时系统应用可能获取更低级API权限,更多权限操作,例如实现静默安装等操作。但无法通过命令行进行卸载,用户只能通过“禁用”功能禁用它们,或通过具有 root 权限的工具手动删除它们。 - 普通应用:安装在设备的
/data/app分区。这些应用通常是用户从应用商店(如 Google Play)下载或手动安装的。应用受到用户严格管控,但是需要权限时需要用户手动赋予权限。
应用权限
- 系统应用:可以拥有更多的权限,甚至是普通应用无法获得的高级权限。因为系统应用在系统层面上运行,能够访问某些受保护的 API 和资源(如读写系统设置、后台静默安装应用、访问低层硬件功能等)。例如,系统应用可以调用
READ_PRIVILEGED_PHONE_STATE这样的受保护权限。 - 普通应用:权限受到用户的严格控制,通常需要用户在安装或使用时授权。普通应用不能使用一些系统级权限,即使申请了也会被拒绝。
安装第三方应用作为系统应用
参考GMS服务安装,安装后的应用不可卸载
创建own文件夹,安装以下目录层级创建
mkdir -p own/system/priv-app
打包Docker
创建Dockerfile文件
FROM redroid/redroid:11.0.0-latest
COPY own /
运行命令 docker build --build-arg userid=$(id -u) --build-arg groupid=$(id -g) --build-arg username=$(id -un) -t redroid-builder-own .