RustDesk Android APK 从零编译全步骤

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 步)

  1. 安装系统依赖
  2. 配置 Git 代理
  3. 安装 Rust 工具链
  4. 安装 Cargo 工具
  5. 安装 Android NDK
  6. 安装 Android SDK
  7. 安装 vcpkg
  8. 拉取 RustDesk 源码
  9. 应用源码补丁
  10. 安装 Flutter SDK
  11. 生成桥接代码
  12. 安装 vcpkg 依赖
  13. 创建 AOM Stub 库
  14. 编译 Rust 原生库
  15. 配置 Gradle 并编译 APK
  16. 验证 APK
  17. 下载 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)。

踩坑经验

  1. bootstrap-vcpkg.sh 必须成功执行并生成 vcpkg 二进制文件。如果失败,检查是否安装了 cmakeg++curl
  2. vcpkg 目录必须初始化为 git 仓库并 commit,否则 vcpkg.json 中的 baseline hash 无法解析。
  3. 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 个包)。

踩坑经验(重要)

  1. 必须使用绝对路径--x-install-root=~/vcpkg/installed 中的 ~ 在某些子进程中不展开,导致安装到错误目录(/home/user/rustdesk/~/vcpkg/installed)。必须写成 --x-install-root=/home/你的用户名/vcpkg/installed
  2. VCPKG_ROOT 也必须用绝对路径
  3. 这一步耗时最长(30-40 分钟),因为需要编译 ffmpeg 等大型库。服务器只有 5.8GB 内存,已设置 VCPKG_MAX_CONCURRENCY=2 防止 OOM。
  4. 如果 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 文件。

踩坑经验

  1. VCPKG_ROOT 必须用绝对路径!如果用 ~/vcpkg,cargo build 传递给 build script 时 ~ 不展开,FFmpeg 等库找不到。
  2. 编译使用 LTO 优化,service 二进制编译会占大量 CPU 和内存(2GB+),耐心等待。
  3. 如果报 could not find native static library "aom",说明 Step 13 的 stub 库未创建或路径不对。
  4. 如果报 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。

踩坑经验

  1. 编译过程中会输出大量 Kotlin 版本不匹配的 warning(Module was compiled with an incompatible version of Kotlin),这是正常的,不影响最终结果。
  2. 如果 Kotlin 编译报语法错误(Syntax error: Expecting member declaration),检查 BootReceiver.ktRootHelper.ktFloatingWindowService.kt 是否有语法问题。
  3. 如果 Dart 编译报错(Target kernel_snapshot_program failed),检查 settings_page.dartserver_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 minapt 下载安装
2配置 Git 代理~10 sec
3安装 Rust~5 minUSTC 镜像下载
4安装 Cargo 工具~10 min编译 3 个工具
5安装 NDK~3 min688MB 下载
6安装 Android SDK~3 min
7安装 vcpkg~5 min含 bootstrap 编译
8拉取源码~2 mintarball 方式
9应用补丁~1 min8 处修改
10安装 Flutter~5 minflutter-io.cn 镜像
11生成桥接代码~3 min含 flutter pub get
12vcpkg 安装依赖~30-40 min最慢的一步
13创建 AOM Stub~30 sec
14编译 Rust 库~15-20 minLTO 优化较慢
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权限,此功能相关代码就不公开了,自行探索。
请添加图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值