首页 / 技术分享 / AI 提示词 /
开发一个 Android VNC 客户端

开发一个 Android VNC 客户端

码不停提

2026-02-22
14 次浏览
0 条评论

从零搭建一个纯 Kotlin 实现的 Android VNC 客户端。采用 MVVM 架构 + Koin DI + Kotlin Coroutines,自实现 RFB 协议(不依赖 C/JNI 库),使用 SurfaceView 渲染远程桌面。

AI 提示词
VNC
Andriod Client
分享:

TL;DR:从零搭建一个纯 Kotlin 实现的 Android VNC 客户端。采用 MVVM 架构 + Koin DI + Kotlin Coroutines,自实现 RFB 协议(不依赖 C/JNI 库),使用 SurfaceView 渲染远程桌面。开发分 14 个步骤,预计 1 人约 10 个工作日完成核心功能。关键技术决策:纯 Kotlin 实现 RFB 协议(参考 bVNC/TightVNC Java Viewer)、编码按 Raw → CopyRect → ZRLE → Tight 渐进实现、32bpp ARGB 像素格式直接映射 Android Bitmap、DataStore 存储设置。


Steps

阶段一:项目脚手架 + 基础 UI(~1 天)

1. 初始化 Android Gradle 项目

  • 创建根目录 build.gradle.kts(AGP 8.7.x + Kotlin 2.0.x 插件声明)
  • 创建 settings.gradle.kts(include :app
  • 创建 gradle.properties(AndroidX 启用、JVM 参数)
  • 创建 gradle/wrapper/gradle-wrapper.properties(Gradle 8.11.1)
  • 下拉 Gradle Wrapper(gradlewgradlew.batgradle-wrapper.jar
  • 创建根 .gitignore
  • 创建 app/build.gradle.kts:
    • namespace = "com.vnclient.app"minSdk = 24compileSdk = 35targetSdk = 35
    • 启用 viewBinding = true
    • 依赖:AndroidX Core KTX、AppCompat、Material、Activity KTX、Lifecycle ViewModel/Runtime、DataStore Preferences、Coroutines Android、Koin Android、Timber
  • 创建 app/src/main/AndroidManifest.xml:声明 INTERNET 权限,注册三个 Activity
  • 创建资源文件:values/strings.xmlvalues/colors.xmlvalues/themes.xml

2. 启动页 + 主页 UI

阶段二:RFB 协议核心(~3 天)

3. TCP Socket 连接 + RFB 握手

  • 创建 vnc/RfbConstants.kt:所有协议常量(消息类型 ID、编码 ID、安全类型 ID)
  • 创建 vnc/RfbConnection.kt:
    • suspend fun connect(host, port) — 在 Dispatchers.IO 上建立 TCP 连接
    • 封装 DataInputStream / DataOutputStream 的读写方法
    • 连接超时 10s,支持 IPv4 和 IPv6(使用 InetSocketAddress 自动解析)
  • 创建 vnc/RfbHandshake.kt:
    • 协议版本协商:解析服务器版本字符串(如 "RFB 003.008\n"),回复支持的版本
    • 安全类型协商:解析服务器支持的安全类型列表,选择 type=2(VNC Auth)或 type=1(None)
    • ClientInit 发送(shared-flag = 1)
    • ServerInit 接收并解析(宽度、高度、像素格式、桌面名称)
  • 创建 model/ConnectionState.kt:密封类 Connecting | Authenticating | Connected | Disconnected(reason) | Error(exception)

4. VNC 密码认证

  • 创建 util/DesEncryptor.kt:
    • DES 加密工具,关键:对密钥每字节做 bit-reversal(VNC 特有的历史设计)
    • 密码取前 8 字节不足补 0 → 每字节 bit-reverse → 作为 DES ECB 密钥 → 加密 16 字节 challenge(分两个 8 字节块)
    • 使用 javax.crypto.Cipher + DESKeySpec
  • 创建 vnc/RfbAuth.kt:
    • 接收 16 字节 challenge → 调用 DesEncryptor → 发送 16 字节 response → 读取 SecurityResult

5. 帧缓冲接收 + Raw 编码渲染

  • 创建 vnc/RfbMessageWriter.kt:
    • sendSetPixelFormat():请求 32bpp true-color ARGB(与 Bitmap.Config.ARGB_8888 直接兼容)
    • sendSetEncodings():声明支持的编码列表
    • sendFramebufferUpdateRequest(incremental, x, y, w, h):请求帧更新
    • sendKeyEvent(downFlag, keySym)
    • sendPointerEvent(buttonMask, x, y)
  • 创建 vnc/RfbMessageReader.kt:
    • 主循环:读取服务器消息类型 → 分发处理
    • 解析 FramebufferUpdate 消息头(矩形数量)→ 逐矩形读取(x, y, w, h, encoding)→ 分发给编码处理器
  • 创建 vnc/encoding/EncodingHandler.kt:编码处理接口 fun decode(input, bitmap, x, y, w, h)
  • 创建 vnc/encoding/RawEncoding.kt:直接读取 w × h × 4 字节像素数据写入 Bitmap
  • 创建 vnc/FramebufferManager.kt:
    • 管理一个可复用的 BitmapBitmap.createBitmap(serverWidth, serverHeight, ARGB_8888)
    • 提供 lockPixels() / unlockPixels() 线程安全访问
    • 跟踪脏矩形区域,通知渲染线程刷新
  • 创建 vnc/RfbClient.kt:
    • 核心协调类,组合以上模块
    • suspend fun start(connectionInfo) → connect → handshake → auth → setPixelFormat → setEncodings → 进入消息循环
    • 通过 StateFlow<ConnectionState> 对外暴露连接状态
    • 通过 SharedFlow 或回调通知帧已更新

阶段三:远程桌面 UI + 交互(~2 天)

6. 远程桌面界面 + SurfaceView 渲染

  • 创建 layout/activity_vnc.xml:全屏 SurfaceView + 顶部状态栏(连接状态指示灯)+ 浮动按钮(断开、键盘)
  • 创建 ui/vnc/VncSurfaceView.kt:
    • 继承 SurfaceView,实现 SurfaceHolder.Callback
    • 渲染线程:帧更新通知到达 → lockCanvas()drawBitmap() with Matrix(缩放远程桌面适配屏幕)→ unlockCanvasAndPost()
    • 维护 Matrix 用于坐标变换(屏幕坐标 ↔ 远程桌面坐标)
  • 创建 ui/vnc/VncActivity.kt:
    • AndroidManifest.xml 中声明 configChanges="orientation|screenSize|keyboardHidden" 防止横竖屏重建 Activity 导致连接断开
    • 全屏模式(隐藏系统状态栏/导航栏)
    • 观察 VncViewModel.connectionState 更新 UI 状态栏颜色(绿/红)
    • 断开按钮 → 调用 viewModel.disconnect() → finish()
  • 创建 ui/vnc/VncViewModel.kt:
    • 持有 RfbClient 实例
    • viewModelScope 内启动连接协程
    • 暴露 connectionState: StateFlowframebuffer: Bitmap 给 UI 层

7. 触摸 → 鼠标事件 + 虚拟键盘

  • 创建 input/TouchHandler.kt:
    • 使用 GestureDetectorCompat + ScaleGestureDetector
    • 单指移动 → PointerEvent(buttonMask=0, x, y)(移动鼠标)
    • 单击 → PointerEvent(buttonMask=1) + PointerEvent(buttonMask=0)(左键按下/释放)
    • 双击 → 两次快速左键
    • 长按 → PointerEvent(buttonMask=4)(右键)
    • 双指滚动 → PointerEvent(buttonMask=8/16)(滚轮上/下)
    • 所有坐标通过 Matrix.invert() 从屏幕坐标映射回远程桌面坐标
  • 创建 input/KeySymMapping.kt:
    • Android KeyCode / Unicode 字符 → X11 KeySym 映射表
    • 第一版覆盖:ASCII 可打印字符(0x20-0x7E)、Enter、Backspace、Tab、Escape、方向键、F1-F12、Ctrl/Alt/Shift
  • 创建 input/KeyboardHandler.kt:
    • VncSurfaceView 重写 onCreateInputConnection() 返回自定义 BaseInputConnection
    • commitText() 中逐字符转 KeySym 发送 KeyEvent
    • 浮动工具栏提供 Ctrl、Alt、Esc、Tab 等特殊键按钮

阶段四:高级编码 + 性能优化(~2 天)

8. CopyRect 编码

  • 创建 vnc/encoding/CopyRectEncoding.kt:读取 src-x, src-y → 在 Bitmap 上 blit 矩形区域(极轻量,处理窗口拖动场景)

9. 增量帧更新 + 帧率控制

  • 修改 RfbClient 消息循环:每次 FramebufferUpdate 处理完毕后才发送下一次 FramebufferUpdateRequest(incremental=true),形成自然背压
  • FramebufferManager 只标记脏矩形,渲染线程只重绘变化区域

10. ZRLE 编码

  • 创建 vnc/encoding/ZRLEEncoding.kt:
    • Zlib 解压(使用 Java 内置 java.util.zip.Inflater,无需外部库)
    • 按 64×64 tile 解析:RLE/Plain RLE/Raw/Palette 子编码
    • 解码后写入 Bitmap 对应区域

11. Tight 编码(JPEG)

  • 创建 vnc/encoding/TightEncoding.kt:
    • 解析 compression-control byte → 判断类型(BasicCompression / FillCompression / JpegCompression)
    • JPEG 类型:读取 JPEG 数据 → BitmapFactory.decodeByteArray() 解码 → 写入帧缓冲
    • Fill 类型:用单色填充矩形
    • Basic 类型:Zlib 解压像素数据

阶段五:连接管理 + 设置存储(~1 天)

12. 断线重连 + 连接状态管理

  • RfbClient 中捕获 IOException → 更新状态为 Disconnected
  • VncActivity 观察到断连后弹出重连对话框(带倒计时自动重连)
  • 重连逻辑:间隔递增重试(1s, 2s, 4s),最多 5 次

13. DataStore 设置 + 连接历史

  • 创建 data/PreferencesManager.kt:
    • 保存/读取上次成功连接的 IP、端口
    • 使用 DataStore<Preferences>
  • 创建 data/ConnectionHistory.kt:
    • 保存最近 5 条连接记录,主页下拉选择
  • MainActivity 启动时自动填充上次连接信息

阶段六:收尾(~1 天)

14. 横竖屏 + 缩放手势

  • VncSurfaceView 中使用 ScaleGestureDetector 实现双指缩放
  • 维护 Matrix(scale + translate),限制缩放范围 0.5x - 3.0x
  • 横屏时自动调整适配比例

15. 一键打包脚本 + 文档

  • 创建 build_debug.sh:
    #!/bin/bash
    cd "$(dirname "$0")"
    ./gradlew assembleDebug
    echo "APK: app/build/outputs/apk/debug/app-debug.apk"
  • 创建 docs/development-plan.md:开发计划文档
  • 创建 docs/design-document.md:项目设计文档(架构图、协议流程、模块说明)

完整文件清单(需新建 ~45 个文件)

类别 数量 关键文件
Gradle 构建 6 build.gradle.kts(root+app)、settings.gradle.ktsgradle.properties、wrapper
Android 配置 1 AndroidManifest.xml
UI 资源 ~10 layouts(3)、values(4)、drawables(2)、mipmap(5)
Kotlin 源码 ~25 Application(1)、DI(1)、Activity+ViewModel(5)、RFB 协议(9)、编码(5)、输入(3)、数据(2)、工具(3)
测试 ~6 握手/认证/消息解析/KeySym/IP验证/DES 单元测试
脚本+文档 3 build_debug.sh、2 个 docs

Verification

  1. 阶段一验证./gradlew assembleDebug 编译通过,App 安装后能看到启动页 → 主页
  2. 阶段二验证:输入 TigerVNC 服务器地址(建议用 TigerVNC 做开发测试服务器),连接成功后看到远程桌面静态画面
  3. 阶段三验证:能用触摸移动鼠标、单击打开应用、弹出虚拟键盘输入文字
  4. 阶段四验证:切换到 Tight/ZRLE 编码后画面流畅、带宽下降
  5. 阶段五验证:拔掉 WiFi 后能自动检测断连并提示重连;重启 App 后自动填充上次 IP
  6. 最终验证:运行 ./build_debug.sh 一键生成 APK,在手机上安装后能完成完整的远程桌面操作

Decisions

  • RFB 实现方式:选择纯 Kotlin 自实现,而非 libvncclient (C/JNI) — 避免 NDK 交叉编译复杂度,自用 App 不需要极致兼容性
  • DI 框架:选择 Koin 而非 Dagger — 配置简单,中小项目足够
  • 渲染方案:选择 SurfaceView 而非 TextureView — 需求指定,独立 Surface 性能更优
  • 存储方案:选择 DataStore 而非 SharedPreferences — Coroutines 友好,无主线程阻塞风险
  • 编码实现顺序:Raw → CopyRect → ZRLE → Tight — 渐进式,先保证连通再优化性能
  • 像素格式:固定请求 32bpp ARGB — 与 Bitmap.Config.ARGB_8888 零转换开销
  • 横竖屏处理configChanges 而非 Activity 重建 — 防止 Socket 连接断开
  • 构建脚本语言:Kotlin DSL (.kts) — 类型安全,IDE 自动补全

评论区 (0)

你需要先 登录 后才能发表评论。
还没有人评论,赶快成为第一个吧。