RustDesk Android APK 从零编译全步骤
在空白 Ubuntu 24.04 服务器上,从第一条命令到生成 APK。本文档基于实际编译验证,包含所有踩坑修复。
| 项目 | 值 |
|---|---|
| RustDesk 版本 | 1.4.8 |
| 目标架构 | arm64-v8a |
| 操作系统 | Ubuntu 24.04 |
| 网络环境 | 中国(需镜像) |
| 服务器内存 | 最低 4GB,推荐 6GB+ |
使用说明:找一个全新的 Ubuntu 24.04 服务器,以普通用户身份登录(需要有 sudo 权限),然后从第 1 步开始,按顺序逐条复制粘贴执行即可。整个过程约需 70-100 分钟(其中大部分是下载和编译等待时间)。
重要提醒:本文档中所有
~在 shell 脚本中会自动展开,但在 Python 脚本、环境变量传递给子进程等场景下可能不展开。遇到路径问题时,请使用/home/你的用户名绝对路径替代~。
步骤总览 (共 17 步)
- 安装系统依赖
- 配置 Git 代理
- 安装 Rust 工具链
- 安装 Cargo 工具
- 安装 Android NDK
- 安装 Android SDK
- 安装 vcpkg
- 拉取 RustDesk 源码
- 应用源码补丁
- 安装 Flutter SDK
- 生成桥接代码
- 安装 vcpkg 依赖
- 创建 AOM Stub 库
- 编译 Rust 原生库
- 配置 Gradle 并编译 APK
- 验证 APK
- 下载 APK 到本地
1. 安装系统依赖
~3 min
安装编译所需的所有系统包:编译器、构建工具、Java、libaom-dev 等。
# 更新包列表
sudo apt-get update -y
# 安装全部依赖
sudo apt-get install -y \
clang cmake curl gcc g++ gcc-multilib git libclang-dev libgtk-3-dev llvm-dev nasm ninja-build \
pkg-config wget unzip zip tree rsync ca-certificates xz-utils file make libssl-dev \
libasound2-dev libunwind-dev openjdk-17-jdk-headless libayatana-appindicator3-dev \
libc6-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libpam0g-dev libpulse-dev \
libva-dev libxcb-randr0-dev libxcb-shape0-dev libxcb-xfixes0-dev libxdo-dev libxfixes-dev \
libaom-dev
说明:
libaom-dev提供 AV1 编解码器头文件(AOM stub 库需要)gcc-multilib提供 32 位兼容头文件(bindgen 需要)openjdk-17-openjdk-amd64是 Gradle 编译需要的 JDK
2. 配置 Git 代理
~10 sec
配置 Git 和 curl 走国内代理(GitHub 在中国直连会超时)。
# Git: GitHub URL → ghfast.top 代理
git config --global url."https://ghfast.top/https://github.com/".insteadOf "https://github.com/"
git config --global submodule.recurse false
# 创建 curl 包装器(vcpkg 用 curl 下载 GitHub tarball,不走 git config)
mkdir -p ~/.local/bin
cat > ~/.local/bin/curl <<'WRAPPER'
#!/bin/bash
REAL_CURL="/usr/bin/curl"
args=()
for arg in "$@"; do
if [[ "$arg" == https://github.com/* ]]; then
arg="https://ghfast.top/$arg"
fi
args+=("$arg")
done
exec "$REAL_CURL" "${args[@]}"
WRAPPER
chmod +x ~/.local/bin/curl
# 强制 pub.dev 走 IPv4(IPv6 在中国会卡住)
echo "$(grep -q 'pub.dev' /etc/hosts || echo '34.36.0.14 pub.dev' | sudo tee -a /etc/hosts)"
3. 安装 Rust 工具链
~5 min
安装 Rust stable(当前 1.96.1),配置 USTC 镜像加速下载。
踩坑经验:不要使用 TUNA(清华)crates.io 镜像!TUNA 镜像走 IPv6,在中国大陆 RTT 300ms+,
cargo metadata会卡 2+ 小时。USTC 镜像或官方 crates.io 均可正常工作。如果 USTC 也慢,直接删掉镜像配置用官方源(crates.io 用 Fastly CDN,中国直连速度尚可)。
# 配置 crates.io 镜像(USTC)
mkdir -p ~/.cargo
cat > ~/.cargo/config.toml <<'EOF'
[source.crates-io]
replace-with = "ustc"
[source.ustc]
registry = "sparse+https://mirrors.ustc.edu.cn/crates.io-index/"
[net]
git-fetch-with-cli = true
EOF
# 安装 Rust(使用 USTC 镜像)
export RUSTUP_DIST_SERVER="https://mirrors.ustc.edu.cn/rust-static"
export RUSTUP_UPDATE_ROOT="https://mirrors.ustc.edu.cn/rust-static/rustup"
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --profile default
# 激活环境
source ~/.cargo/env
# 安装 Android 交叉编译 target
rustup target add aarch64-linux-android
# 验证
rustc --version
cargo --version
备用方案:如果
cargo metadata在后续步骤中卡住超过 10 分钟,说明镜像有问题。执行以下命令切换到官方 crates.io:cat > ~/.cargo/config.toml <<'EOF' [net] git-fetch-with-cli = true EOF
4. 安装 Cargo 工具
~10 min
安装 cargo-ndk(Android 交叉编译)、cargo-expand、flutter_rust_bridge_codegen(桥接代码生成器)。
source ~/.cargo/env
# cargo-ndk: Rust Android 交叉编译工具
cargo install cargo-ndk --version 3.1.2 --locked
# cargo-expand: 宏展开工具
cargo install cargo-expand --version 1.0.95 --locked
# flutter_rust_bridge_codegen: Rust-Dart 桥接代码生成
cargo install flutter_rust_bridge_codegen --version 1.80.1 --features uuid --locked
# 验证
cargo ndk --version
flutter_rust_bridge_codegen --version
5. 安装 Android NDK
~3 min
下载并解压 NDK r28c(688MB)。
cd ~
# Google 域名在中国通常可直连,如果超时可加 --connect-timeout 30 重试
curl -fL "https://dl.google.com/android/repository/android-ndk-r28c-linux.zip" -o /tmp/ndk.zip
unzip -q /tmp/ndk.zip -d ~
rm -f /tmp/ndk.zip
# 验证
ls ~/android-ndk-r28c/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android24-clang
注意:如果
dl.google.com也无法访问,可尝试使用国内镜像:curl -fL "https://mirrors.cloud.tencent.com/AndroidSDK/android-ndk-r28c-linux.zip" -o /tmp/ndk.zip
6. 安装 Android SDK
~3 min
安装 Android commandline-tools、platform-tools、android-34、build-tools。
mkdir -p ~/Android/Sdk/cmdline-tools
cd /tmp
curl -fL "https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip" -o clt.zip
unzip -q clt.zip -d clt
mv clt/cmdline-tools ~/Android/Sdk/cmdline-tools/latest
rm -rf clt clt.zip
# 设置环境变量(建议加入 ~/.bashrc)
export ANDROID_HOME=~/Android/Sdk
export ANDROID_SDK_ROOT=~/Android/Sdk
export ANDROID_NDK_HOME=~/android-ndk-r28c
export ANDROID_NDK_ROOT=~/android-ndk-r28c
export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
SDKMANAGER="$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager"
yes | "$SDKMANAGER" --licenses >/dev/null 2>&1
"$SDKMANAGER" "platform-tools" "platforms;android-34" "build-tools;34.0.0"
# 验证
"$SDKMANAGER" --list_installed
7. 安装 vcpkg
~5 min
下载 vcpkg,编译 vcpkg 二进制,初始化 git 仓库(vcpkg.json baseline 需要 git)。
踩坑经验:
bootstrap-vcpkg.sh必须成功执行并生成vcpkg二进制文件。如果失败,检查是否安装了cmake、g++、curl。- vcpkg 目录必须初始化为 git 仓库并 commit,否则
vcpkg.json中的 baseline hash 无法解析。- git commit hash 必须与
vcpkg.json中的 baseline 一致(后续步骤会自动更新)。
cd ~
VCPKG_COMMIT="120deac3062162151622ca4860575a33844ba10b"
# 下载 vcpkg 源码
curl -fL "https://codeload.github.com/microsoft/vcpkg/tar.gz/$VCPKG_COMMIT" -o /tmp/vcpkg.tar.gz
mkdir -p ~/vcpkg
tar -xzf /tmp/vcpkg.tar.gz -C ~/vcpkg --strip-components=1
rm -f /tmp/vcpkg.tar.gz
# 编译 vcpkg(这一步必须成功!)
cd ~/vcpkg
./bootstrap-vcpkg.sh -disableMetrics
# 验证 vcpkg 二进制存在且可执行
./vcpkg --version
# 初始化 git 仓库(vcpkg.json 的 baseline 需要 git show)
git init -q
git add -A
git -c user.email=build@local -c user.name=build commit -q -m "vcpkg at $VCPKG_COMMIT"
8. 拉取 RustDesk 源码
~2 min
下载 RustDesk 1.4.8 源码和 hbb_common 子模块(使用 tarball 方式,避免 git clone 超时)。
cd ~
RUSTDESK_COMMIT="049781400460ebdd19577fa705af17e848f9ecef"
HBB_COMMIT="a920d00945e1d2441b3f77b2677054cb8c3d9dd2"
# 下载 RustDesk 主源码
curl -fL "https://codeload.github.com/rustdesk/rustdesk/tar.gz/$RUSTDESK_COMMIT" -o /tmp/rustdesk.tar.gz
mkdir -p ~/rustdesk
tar -xzf /tmp/rustdesk.tar.gz -C ~/rustdesk --strip-components=1
rm -f /tmp/rustdesk.tar.gz
# 下载 hbb_common 子模块
curl -fL "https://codeload.github.com/rustdesk/hbb_common/tar.gz/$HBB_COMMIT" -o /tmp/hbb.tar.gz
mkdir -p ~/rustdesk/libs/hbb_common
tar -xzf /tmp/hbb.tar.gz -C ~/rustdesk/libs/hbb_common --strip-components=1
rm -f /tmp/hbb.tar.gz
# 初始化 git(build.rs 的 gen_version() 需要读取 commit hash)
cd ~/rustdesk
git init -q
git add -A
git -c user.email=build@local -c user.name=build commit -q -m "build $RUSTDESK_COMMIT"
# 验证
ls ~/rustdesk/Cargo.toml
ls ~/rustdesk/libs/hbb_common/Cargo.toml
9. 应用源码补丁
~1 min
由于中国网络环境,需要修改 8 处源码才能编译。逐条执行以下命令:
9.1 替换 webm 依赖
sed -i 's|webm = { git = "https://github.com/rustdesk-org/rust-webm" }|webm = "1.1"|' \
~/rustdesk/libs/scrap/Cargo.toml
9.2 替换 tray-icon 依赖
sed -i 's|tray-icon = { git = "https://github.com/tauri-apps/tray-icon", version = "0.21.3" }|tray-icon = "0.21.3"|' \
~/rustdesk/Cargo.toml
9.3 从 vcpkg.json 移除 aom
python3 -c "
import json
with open('$HOME/rustdesk/vcpkg.json') as f:
data = json.load(f)
data['dependencies'] = [d for d in data['dependencies'] if d.get('name') != 'aom']
with open('$HOME/rustdesk/vcpkg.json', 'w') as f:
json.dump(data, f, indent=2)
print('aom removed from vcpkg.json')
"
9.4 libyuv 改用 GitHub 镜像
LIBYUV_REF="0faf8dd0e004520a61a603a4d2996d5ecc80dc3f"
# 下载 libyuv 源码并创建本地 git 仓库
curl -fL "https://ghfast.top/https://github.com/lemenkov/libyuv/archive/${LIBYUV_REF}.tar.gz" -o /tmp/libyuv.tar.gz
mkdir -p /tmp/libyuv-git
cd /tmp/libyuv-git
tar -xzf /tmp/libyuv.tar.gz --strip-components=1
rm -f /tmp/libyuv.tar.gz
git init -q && git add -A
git -c user.email=build@local -c user.name=build commit -q -m "libyuv at ${LIBYUV_REF}"
# 修改 portfile 指向本地 git
LIBYUV_LOCAL_HASH=$(git -C /tmp/libyuv-git rev-parse HEAD)
LIBYUV_PORTFILE="$HOME/rustdesk/res/vcpkg/libyuv/portfile.cmake"
sed -i "s|URL https://chromium.googlesource.com/libyuv/libyuv|URL file:///tmp/libyuv-git|g" "$LIBYUV_PORTFILE"
sed -i "s|REF $LIBYUV_REF|REF $LIBYUV_LOCAL_HASH|g" "$LIBYUV_PORTFILE"
cd ~/rustdesk
9.5 更新 vcpkg.json baseline hash
# vcpkg.json 中的 baseline 指向原 commit,需更新为本地 git hash
VCPKG_NEW_HASH=$(git -C ~/vcpkg rev-parse HEAD)
VCPKG_OLD_HASH="120deac3062162151622ca4860575a33844ba10b"
sed -i "s/$VCPKG_OLD_HASH/$VCPKG_NEW_HASH/g" ~/rustdesk/vcpkg.json
9.6 APK 签名改为 debug
sed -i 's/signingConfigs.release/signingConfigs.debug/g' \
~/rustdesk/flutter/android/app/build.gradle
sed -i 's/org.gradle.jvmargs=-Xmx1024M/org.gradle.jvmargs=-Xmx2g/g' \
~/rustdesk/flutter/android/gradle.properties
10. 安装 Flutter SDK
~5 min
下载 Flutter 3.24.5(使用国内镜像),应用 dropdown 补丁。
cd ~
export FLUTTER_STORAGE_BASE_URL="https://storage.flutter-io.cn"
# 下载 Flutter 3.24.5
curl -fL "https://storage.flutter-io.cn/flutter_infra_release/releases/stable/linux/flutter_linux_3.24.5-stable.tar.xz" -o /tmp/flutter.tar.xz
tar -xf /tmp/flutter.tar.xz -C ~
rm -f /tmp/flutter.tar.xz
# 移除 origin remote,避免 flutter --version 去 GitHub 拉取
git -C ~/flutter remote remove origin 2>/dev/null || true
# 应用 dropdown 补丁
cd ~/flutter
git apply ~/rustdesk/.github/patches/flutter_3.24.4_dropdown_menu_enableFilter.diff 2>/dev/null && echo "patch applied" || echo "patch skipped"
# 配置 Android SDK/NDK 路径
export PATH="$HOME/flutter/bin:$HOME/.cargo/bin:$HOME/.local/bin:$PATH"
flutter config --android-sdk ~/Android/Sdk --android-ndk ~/android-ndk-r28c 2>/dev/null || true
yes | flutter doctor --android-licenses >/dev/null 2>&1 || true
# 验证
flutter --version
11. 生成桥接代码
~3 min
运行 flutter pub get 下载 Dart 依赖,然后生成 Rust-Dart 桥接代码。
踩坑经验:
cargo metadata在这一步可能卡住。如果超过 5 分钟没完成,检查~/.cargo/config.toml中的镜像配置。TUNA 镜像走 IPv6 极慢,切换到 USTC 或官方 crates.io。
export PATH="$HOME/flutter/bin:$HOME/.cargo/bin:$HOME/.local/bin:$PATH"
export FLUTTER_STORAGE_BASE_URL="https://storage.flutter-io.cn"
# 下载 Dart 依赖
cd ~/rustdesk/flutter
flutter pub get
# 生成桥接代码
cd ~/rustdesk
flutter_rust_bridge_codegen \
--rust-input ./src/flutter_ffi.rs \
--dart-output ./flutter/lib/generated_bridge.dart \
--c-output ./flutter/macos/Runner/bridge_generated.h
# 复制头文件到 iOS 目录
cp -f ./flutter/macos/Runner/bridge_generated.h ./flutter/ios/Runner/bridge_generated.h 2>/dev/null || true
# 验证
ls -la ~/rustdesk/src/bridge_generated.rs ~/rustdesk/src/bridge_generated.io.rs \
~/rustdesk/flutter/lib/generated_bridge.dart
12. 安装 vcpkg 依赖
~30-40 min
使用 vcpkg 安装 arm64-android 的 C/C++ 依赖库(ffmpeg、libvpx、libyuv、opus、oboe 等 17 个包)。
踩坑经验(重要):
- 必须使用绝对路径!
--x-install-root=~/vcpkg/installed中的~在某些子进程中不展开,导致安装到错误目录(/home/user/rustdesk/~/vcpkg/installed)。必须写成--x-install-root=/home/你的用户名/vcpkg/installed。- VCPKG_ROOT 也必须用绝对路径。
- 这一步耗时最长(30-40 分钟),因为需要编译 ffmpeg 等大型库。服务器只有 5.8GB 内存,已设置
VCPKG_MAX_CONCURRENCY=2防止 OOM。- 如果 vcpkg 报 “file system lock” 错误,先
pkill -9 -f vcpkg,删除~/vcpkg/.vcpkg-root,再重试。
# 使用绝对路径!不要用 ~!
export VCPKG_ROOT=/home/$USER/vcpkg
export VCPKG_DEFAULT_TRIPLET=arm64-android
export VCPKG_MAX_CONCURRENCY=2
export PATH="$HOME/.local/bin:$PATH"
export ANDROID_NDK_HOME=/home/$USER/android-ndk-r28c
cd ~/rustdesk
/home/$USER/vcpkg/vcpkg install --triplet arm64-android --x-install-root=/home/$USER/vcpkg/installed
# 验证
ls ~/vcpkg/installed/arm64-android/lib/
应看到以下 .a 文件:
libavcodec.a libavformat.a libavutil.a libcpu_features.a
libjpeg.a libndk_compat.a liboboe.a libopus.a
libturbojpeg.a libvpx.a libyuv.a
13. 创建 AOM Stub 库
~30 sec
scrap/build.rs 需要 aom 头文件和 libaom.a。由于 aom 源码(aomedia.googlesource.com)被墙,从 libaom-dev 复制头文件,创建所有函数返回错误码的 stub 库。运行时 AV1 不可用,自动回退到 VP8/VP9/H264。
踩坑经验:AOM stub 代码中有一个关键类型名:必须用
aom_ext_part_funcs_t(带下划线),不是aom_extpart_funcs_t。头文件aom_external_partition.h中定义的是aom_ext_part_funcs_t。
# 1. 复制头文件
mkdir -p ~/vcpkg/installed/arm64-android/include/aom
cp /usr/include/aom/*.h ~/vcpkg/installed/arm64-android/include/aom/
# 2. 创建 stub C 文件
cat > /tmp/aom_stubs.c <<'STUBEOF'
#include <aom/aom.h>
#include <aom/aom_image.h>
#include <aom/aom_integer.h>
#include <aom/aom_codec.h>
#include <aom/aom_external_partition.h>
#include <aom/aom_frame_buffer.h>
#include <aom/aom_encoder.h>
#include <aom/aom_decoder.h>
#include <aom/aomcx.h>
#include <aom/aomdx.h>
#include <stddef.h>
#define AOM_ERR ((aom_codec_err_t)-1)
int aom_codec_version(void){return 0;}
const char *aom_codec_version_str(void){return "stub";}
const char *aom_codec_version_extra_str(void){return "";}
const char *aom_codec_build_config(void){return "";}
const char *aom_codec_iface_name(aom_codec_iface_t *iface){return "stub";}
const char *aom_codec_err_to_string(aom_codec_err_t err){return "stub";}
const char *aom_codec_error(const aom_codec_ctx_t *ctx){return "stub";}
const char *aom_codec_error_detail(const aom_codec_ctx_t *ctx){return NULL;}
aom_codec_err_t aom_codec_destroy(aom_codec_ctx_t *ctx){return AOM_ERR;}
aom_codec_caps_t aom_codec_get_caps(aom_codec_iface_t *iface){return 0;}
aom_codec_err_t aom_codec_control(aom_codec_ctx_t *ctx,int ctrl_id,...){return AOM_ERR;}
aom_codec_err_t aom_codec_set_option(aom_codec_ctx_t *ctx,const char *n,const char *v){return AOM_ERR;}
const char *aom_obu_type_to_string(OBU_TYPE type){return "stub";}
aom_codec_iface_t *aom_codec_av1_cx(void){return NULL;}
aom_codec_iface_t *aom_codec_av1_dx(void){return NULL;}
aom_codec_err_t aom_codec_dec_init_ver(aom_codec_ctx_t *ctx,aom_codec_iface_t *i,const aom_codec_dec_cfg_t *c,aom_codec_flags_t f,int v){return AOM_ERR;}
aom_codec_err_t aom_codec_peek_stream_info(aom_codec_iface_t *i,const uint8_t *d,size_t s,aom_codec_stream_info_t *si){return AOM_ERR;}
aom_codec_err_t aom_codec_get_stream_info(aom_codec_ctx_t *ctx,aom_codec_stream_info_t *si){return AOM_ERR;}
aom_codec_err_t aom_codec_decode(aom_codec_ctx_t *ctx,const uint8_t *d,size_t s,void *p){return AOM_ERR;}
aom_image_t *aom_codec_get_frame(aom_codec_ctx_t *ctx,aom_codec_iter_t *iter){return NULL;}
aom_codec_err_t aom_codec_set_frame_buffer_functions(aom_codec_ctx_t *ctx,aom_get_frame_buffer_cb_fn_t g,aom_release_frame_buffer_cb_fn_t r,void *p){return AOM_ERR;}
aom_codec_err_t aom_codec_enc_init_ver(aom_codec_ctx_t *ctx,aom_codec_iface_t *i,const aom_codec_enc_cfg_t *c,aom_codec_flags_t f,int v){return AOM_ERR;}
aom_codec_err_t aom_codec_enc_config_default(aom_codec_iface_t *i,aom_codec_enc_cfg_t *c,unsigned int u){return AOM_ERR;}
aom_codec_err_t aom_codec_enc_config_set(aom_codec_ctx_t *ctx,const aom_codec_enc_cfg_t *c){return AOM_ERR;}
aom_fixed_buf_t *aom_codec_get_global_headers(aom_codec_ctx_t *ctx){return NULL;}
aom_codec_err_t aom_codec_encode(aom_codec_ctx_t *ctx,const aom_image_t *img,aom_codec_pts_t p,unsigned long d,aom_enc_frame_flags_t f){return AOM_ERR;}
aom_codec_err_t aom_codec_set_cx_data_buf(aom_codec_ctx_t *ctx,const aom_fixed_buf_t *b,unsigned int s,unsigned int e){return AOM_ERR;}
const aom_codec_cx_pkt_t *aom_codec_get_cx_data(aom_codec_ctx_t *ctx,aom_codec_iter_t *iter){return NULL;}
const aom_image_t *aom_codec_get_preview_frame(aom_codec_ctx_t *ctx){return NULL;}
aom_image_t *aom_img_alloc(aom_image_t *img,aom_img_fmt_t f,unsigned int w,unsigned int h,unsigned int a){return NULL;}
aom_image_t *aom_img_wrap(aom_image_t *img,aom_img_fmt_t f,unsigned int w,unsigned int h,unsigned int a,unsigned char *d){return NULL;}
aom_image_t *aom_img_alloc_with_border(aom_image_t *img,aom_img_fmt_t f,unsigned int w,unsigned int h,unsigned int a,unsigned int sa,unsigned int b){return NULL;}
int aom_img_set_rect(aom_image_t *img,unsigned int x,unsigned int y,unsigned int w,unsigned int h,unsigned int b){return -1;}
void aom_img_flip(aom_image_t *img){}
void aom_img_free(aom_image_t *img){}
int aom_img_plane_width(const aom_image_t *img,int p){return 0;}
int aom_img_plane_height(const aom_image_t *img,int p){return 0;}
int aom_img_add_metadata(aom_image_t *img,uint32_t t,const uint8_t *d,size_t s,aom_metadata_insert_flags_t f){return -1;}
const aom_metadata_t *aom_img_get_metadata(const aom_image_t *img,size_t i){return NULL;}
size_t aom_img_num_metadata(const aom_image_t *img){return 0;}
void aom_img_remove_metadata(aom_image_t *img){}
aom_metadata_t *aom_img_metadata_alloc(uint32_t t,const uint8_t *d,size_t s,aom_metadata_insert_flags_t f){return NULL;}
void aom_img_metadata_free(aom_metadata_t *m){}
size_t aom_uleb_size_in_bytes(uint64_t v){return 0;}
int aom_uleb_decode(const uint8_t *b,size_t a,uint64_t *v,size_t *l){return -1;}
int aom_uleb_encode(uint64_t v,size_t a,uint8_t *c,size_t *s){return -1;}
int aom_uleb_encode_fixed_size(uint64_t v,size_t a,size_t p,uint8_t *c,size_t *s){return -1;}
aom_codec_err_t aom_codec_set_external_partition(aom_codec_ctx_t *ctx,aom_ext_part_funcs_t *f,void *p){return AOM_ERR;}
STUBEOF
# 3. 用 NDK clang 编译
NDK_CLANG=~/android-ndk-r28c/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android24-clang
NDK_AR=~/android-ndk-r28c/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar
$NDK_CLANG -c -O2 -I~/vcpkg/installed/arm64-android/include /tmp/aom_stubs.c -o /tmp/aom_stubs.o
$NDK_AR rcs ~/vcpkg/installed/arm64-android/lib/libaom.a /tmp/aom_stubs.o
# 验证
ls -la ~/vcpkg/installed/arm64-android/lib/libaom.a
14. 编译 Rust 原生库
~15-20 min(首次),~5 min(增量)
使用 cargo-ndk 交叉编译 Rust 代码为 Android arm64 的 .so 文件。
踩坑经验:
- VCPKG_ROOT 必须用绝对路径!如果用
~/vcpkg,cargo build 传递给 build script 时~不展开,FFmpeg 等库找不到。- 编译使用 LTO 优化,
service二进制编译会占大量 CPU 和内存(2GB+),耐心等待。- 如果报
could not find native static library "aom",说明 Step 13 的 stub 库未创建或路径不对。- 如果报
fatal error: 'libavcodec/avcodec.h' file not found,说明 Step 12 的 vcpkg install 未完成或安装到了错误路径。
source ~/.cargo/env
# 必须用绝对路径!
export ANDROID_NDK_HOME=/home/$USER/android-ndk-r28c
export ANDROID_NDK_ROOT=/home/$USER/android-ndk-r28c
export VCPKG_ROOT=/home/$USER/vcpkg
export LIBCLANG_PATH=/usr/lib/x86_64-linux-gnu
export BINDGEN_EXTRA_CLANG_ARGS="-I/usr/include/x86_64-linux-gnu"
export CARGO_BUILD_JOBS=3
cd ~/rustdesk
cargo ndk --platform 21 --target aarch64-linux-android build --locked --release --features flutter,hwcodec
# 验证
ls -la ~/rustdesk/target/aarch64-linux-android/release/liblibrustdesk.so
# 复制 .so 到 jniLibs
JNI_DIR=~/rustdesk/flutter/android/app/src/main/jniLibs/arm64-v8a
mkdir -p "$JNI_DIR"
cp -f ~/rustdesk/target/aarch64-linux-android/release/liblibrustdesk.so "$JNI_DIR/librustdesk.so"
# 复制 libc++_shared.so
cp -f ~/android-ndk-r28c/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/aarch64-linux-android/libc++_shared.so "$JNI_DIR/"
ls -la "$JNI_DIR/"
应看到两个文件:librustdesk.so(~26MB)和 libc++_shared.so(~9MB)。
15. 配置 Gradle 镜像并编译 APK
~10 min
将 Gradle 发行包和 Maven 仓库改为国内镜像,然后构建 APK。
踩坑经验:
- 编译过程中会输出大量 Kotlin 版本不匹配的 warning(
Module was compiled with an incompatible version of Kotlin),这是正常的,不影响最终结果。- 如果 Kotlin 编译报语法错误(
Syntax error: Expecting member declaration),检查BootReceiver.kt、RootHelper.kt、FloatingWindowService.kt是否有语法问题。- 如果 Dart 编译报错(
Target kernel_snapshot_program failed),检查settings_page.dart、server_page.dart是否有语法错误。
15.1 配置 Gradle 镜像
# Gradle 发行包 URL → 腾讯云
sed -i 's/services.gradle.org/mirrors.cloud.tencent.com/; s/distributions/gradle/' \
~/rustdesk/flutter/android/gradle/wrapper/gradle-wrapper.properties
# Maven 仓库 → 阿里云 (build.gradle)
sed -i 's|google()|maven { url "https://maven.aliyun.com/repository/google" }|g' \
~/rustdesk/flutter/android/build.gradle
sed -i 's|jcenter()|maven { url "https://maven.aliyun.com/repository/public" }|g' \
~/rustdesk/flutter/android/build.gradle
# Maven 仓库 → 阿里云 (settings.gradle)
sed -i 's|google()|maven { url "https://maven.aliyun.com/repository/google" }|g' \
~/rustdesk/flutter/android/settings.gradle
sed -i 's|mavenCentral()|maven { url "https://maven.aliyun.com/repository/central" }|g' \
~/rustdesk/flutter/android/settings.gradle
sed -i 's|gradlePluginPortal()|maven { url "https://maven.aliyun.com/repository/gradle-plugin" }|g' \
~/rustdesk/flutter/android/settings.gradle
15.2 构建 APK
export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
export ANDROID_HOME=~/Android/Sdk
export ANDROID_NDK_HOME=~/android-ndk-r28c
export FLUTTER_STORAGE_BASE_URL="https://storage.flutter-io.cn"
export PATH="$JAVA_HOME/bin:$HOME/flutter/bin:$HOME/.cargo/bin:$HOME/.local/bin:$PATH"
cd ~/rustdesk/flutter
flutter build apk --release --target-platform android-arm64 --split-per-abi
16. 验证 APK
~10 sec
确认 APK 文件已生成并复制到 home 目录。
# 复制 APK 到 home 目录
cp -f ~/rustdesk/flutter/build/app/outputs/flutter-apk/app-arm64-v8a-release.apk \
~/rustdesk-1.4.8-arm64.apk
# 验证
ls -la ~/rustdesk-1.4.8-arm64.apk
sha256sum ~/rustdesk-1.4.8-arm64.apk
应看到约 25MB 的 APK 文件。编译完成!
17. 下载 APK 到本地电脑
~30 sec
在你的 Windows/Mac 电脑上执行以下命令下载 APK:
# Windows PowerShell(会提示输入服务器密码)
scp yujing@服务器IP:~/rustdesk-1.4.8-arm64.apk C:\Users\你的用户名\Desktop\
# 输入服务器密码
# Mac / Linux
scp yujing@服务器IP:~/rustdesk-1.4.8-arm64.apk ~/Desktop/
提示:如果 Windows 上没有
scp命令,可以使用 WinSCP、FileZilla 等 GUI 工具,或者用 Python 脚本通过 paramiko 库下载。
总耗时估算
| 步骤 | 名称 | 耗时 | 备注 |
|---|---|---|---|
| 1 | 安装系统依赖 | ~3 min | apt 下载安装 |
| 2 | 配置 Git 代理 | ~10 sec | |
| 3 | 安装 Rust | ~5 min | USTC 镜像下载 |
| 4 | 安装 Cargo 工具 | ~10 min | 编译 3 个工具 |
| 5 | 安装 NDK | ~3 min | 688MB 下载 |
| 6 | 安装 Android SDK | ~3 min | |
| 7 | 安装 vcpkg | ~5 min | 含 bootstrap 编译 |
| 8 | 拉取源码 | ~2 min | tarball 方式 |
| 9 | 应用补丁 | ~1 min | 8 处修改 |
| 10 | 安装 Flutter | ~5 min | flutter-io.cn 镜像 |
| 11 | 生成桥接代码 | ~3 min | 含 flutter pub get |
| 12 | vcpkg 安装依赖 | ~30-40 min | 最慢的一步 |
| 13 | 创建 AOM Stub | ~30 sec | |
| 14 | 编译 Rust 库 | ~15-20 min | LTO 优化较慢 |
| 15 | 构建 APK | ~10 min | 含 Gradle 下载 |
| 16 | 验证 APK | ~10 sec | |
| 17 | 下载 APK | ~30 sec | |
| 合计 | ~90-110 min |
常见问题排查
Q: cargo metadata 卡住超过 10 分钟
A: crates.io 镜像问题。TUNA(清华)镜像走 IPv6 极慢。切换到 USTC 镜像或官方 crates.io:
cat > ~/.cargo/config.toml <<'EOF'
[net]
git-fetch-with-cli = true
EOF
Q: vcpkg install 报 “file system lock”
A: 有残留的 vcpkg 进程。执行:
pkill -9 -f vcpkg; pkill -9 -f cmake; pkill -9 -f ninja
rm -f ~/vcpkg/.vcpkg-root
然后重新运行 vcpkg install。
Q: Rust 编译报 could not find native static library "aom"
A: AOM stub 库未创建。重新执行 Step 13。注意类型名是 aom_ext_part_funcs_t(带下划线)。
Q: Rust 编译报 fatal error: 'libavcodec/avcodec.h' file not found
A: vcpkg install 未完成或安装到了错误路径。检查 ls ~/vcpkg/installed/arm64-android/include/libavcodec/avcodec.h。如果不存在,重新执行 Step 12(注意用绝对路径)。
Q: Gradle 编译报 Kotlin 语法错误
A: 检查 Root 自动化修改的 Kotlin 文件。常见问题:
BootReceiver.kt: Root 代码必须放在onReceive方法内部RootHelper.kt: Kotlin 字符串中不支持\|转义,改为|FloatingWindowService.kt: 需要添加createView1Pixel方法
Q: Gradle 编译报 Dart 语法错误
A: 检查 Root 自动化修改的 Dart 文件。常见问题:
settings_page.dart: Root 自动化 tile 必须在enhancementsTiles列表定义之后添加server_page.dart:onPressed回调不能有残留的else块cn.rs/template.rs: 翻译条目必须有逗号结尾和正确缩进
Q: APK 构建成功但开机不自动启动服务
A: FFI.startService() 需要先调用 FFI.startServer() 初始化 Rust 运行时。在 BootReceiver.startWithRoot() 和 MainService.onStartCommand() 中,调用 FFI.startService() 之前必须先调用 FFI.startServer(appDir, "") 并等待几秒。
新增功能给自己的开发板使用
修改代码后,重新编译,获取新功能,使得能非常顺手的远程自己的开发板,前提是开发板得有root权限,此功能相关代码就不公开了,自行探索。

2457

被折叠的 条评论
为什么被折叠?



