uuremote-integration-guide
把 UU 远程远控能力接入你的 App —— 设备绑定与发起远控的 URL Scheme 协议说明,覆盖全屏与画中画模式,含 iOS / Android 示例代码与完整错误码。
概述
本文档描述第三方 App 与 UU 远程客户端之间的跨 App 交互协议。第三方 App 可以运行在 iOS 或 Android:iOS 通过 URL Scheme 拉起 UU 远程,Android 通过 Deeplink / Intent 拉起 UU 远程。
本文中的平台范围需要区分:
- 调用端平台:第三方 App 支持 iOS 和 Android,调用方式分别见对应平台示例。
- 目标设备平台:设备绑定和发起远控目前仅支持 Windows 与 macOS 被控设备。Android、iOS、TV 等其他平台设备不会出现在绑定选择列表中,也不属于本协议支持的远控目标。
协议提供两个核心能力:
- 设备绑定:第三方 App 拉起 UU 远程,用户从当前账号下的 Windows/macOS 设备中选择一台进行绑定,UU 远程返回加密后的设备标识。
- 发起远控:第三方 App 使用已绑定的设备标识发起远控连接,支持全屏和画中画小窗两种模式。
UU 远程 URL Scheme:uuremote
最低版本要求:iOS 与 Android 客户端均从 4.42.0 起支持本协议。
1. 安装检测
1.1 iOS 前置配置
iOS 第三方 App 需在 Info.plist 中添加 LSApplicationQueriesSchemes 白名单,否则 canOpenURL 将始终返回 false。
<key>LSApplicationQueriesSchemes</key> <array> <string>uuremote</string> </array>
1.2 iOS 检测方法
func isUURemoteInstalled() -> Bool { guard let url = URL(string: "uuremote://external") else { return false } return UIApplication.shared.canOpenURL(url) }
1.3 iOS 未安装处理
若检测结果为 false,应停止发起绑定或远控请求,并引导用户前往 App Store 安装 UU 远程。
1.4 Android 前置配置
Android 11(API 30)及以上的第三方 App 需在 AndroidManifest.xml 中声明 <queries>,否则应用可见性限制可能导致安装检测失败。
<queries> <intent> <action android:name="android.intent.action.VIEW" /> <data android:scheme="uuremote" /> </intent> </queries>
1.5 Android 检测方法
fun isUURemoteInstalled(context: Context): Boolean { val intent = Intent( Intent.ACTION_VIEW, Uri.parse("uuremote://external") ) return intent.resolveActivity(context.packageManager) != null }
UU 远程 Android 正式包名为 com.netease.uuremote。包名可用于安装引导或应用市场跳转,不建议仅通过包名查询代替 Deeplink 可用性检测。
1.6 Android 未安装处理
若 Intent 无法解析,应停止发起绑定或远控请求,并按接入方既有分发渠道引导用户安装 UU 远程。
2. 设备绑定
2.1 触发方式
第三方 App 通过以下 URL Scheme 拉起 UU 远程的设备绑定流程:
uuremote://external/device/authorize?callback=<回调URL>&nonce=<随机ID>
2.2 请求参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
callback |
String | 是 | 第三方 App 的回调 URL(含 scheme 和 host)。UU 远程完成操作后通过此 URL 回跳。参数值需进行 URL 编码。 |
nonce |
String | 否 | 用于匹配请求和回调的随机唯一标识符,建议使用 UUID。传入后,UU 远程会在回调中原样返回。 |
2.3 iOS 调用示例
let callback = "myapp://bindresult" let nonce = UUID().uuidString let encodedCallback = callback.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? callback let urlString = "uuremote://external/device/authorize?callback=\(encodedCallback)&nonce=\(nonce)" if let url = URL(string: urlString) { UIApplication.shared.open(url) }
2.4 UU 远程内部流程
拉起 UU 远程
|
+-- 未登录或首页未就绪 -> 排队等待,最长 180 秒
| +-- 期间被新的绑定请求取代,或同批只保留最新一条 -> 回调 error=superseded
| +-- 排队期间用户退出登录 -> 回调 error=logout
| +-- 窗口内仍未完成登录 -> 回调 error=timeout
|
+-- 登录态与首页条件满足 -> 进入绑定流程
|
+-- 首页设备列表加载中 -> 等待加载完成,最长 15 秒 -> 超时回调 error=timeout
|
+-- 过滤设备平台,仅保留 Windows/macOS 设备
|
+-- 过滤后设备列表为空 -> 展示“当前无可关联设备”提示 -> 用户点击确认后回调 error=no_device
|
+-- 过滤后设备列表非空 -> 弹出设备选择页面
+-- 用户选择设备并确认 -> 回调 anonymous_device_id
+-- 用户取消(关闭按钮 / 下拉关闭 / 页面被关闭)-> 回调 error=user_cancel
|
+-- 页面栈缺失或无可用展示宿主 -> 回调 error=internal_error
Android、iOS、TV 等非 Windows/macOS 设备不展示在绑定列表中,不能通过本协议生成 anonymous_device_id。
2.5 回调参数
UU 远程通过打开第三方 App 的 callback URL 返回结果。
绑定成功
<callback>?anonymous_device_id=<加密设备ID>&nonce=<原始nonce>
| 参数 | 说明 |
|---|---|
anonymous_device_id |
加密后的 Windows/macOS 设备标识。第三方 App 应持久化存储此值,后续发起远控时使用。 |
nonce |
请求中传入 nonce 时原样返回;未传入时回传空值(字段仍会出现)。 |
绑定失败
<callback>?error=<错误码>&nonce=<原始nonce>
error 值 |
说明 |
|---|---|
no_device |
当前 UU 账号下没有可关联的 Windows/macOS 设备 |
user_cancel |
用户主动取消,包括点击关闭按钮或下拉关闭 |
timeout |
排队等待登录(180 秒)或设备列表加载(15 秒)超时 |
not_logined |
排队被触发时已不满足登录态或首页条件,无法继续 |
superseded |
该绑定请求被后发的同类请求取代,或同批请求中未被选中 |
logout |
排队期间用户退出登录,绑定请求被清理 |
internal_error |
UU 远程内部异常,无法继续处理(页面栈缺失、无可用展示宿主等),不再细分内部原因 |
绑定成功与失败互斥:成功返回 anonymous_device_id,失败返回 error。internal_error 为兜底错误码,UU 远程不向第三方暴露具体内部原因。
2.6 iOS 回调接收示例
第三方 App 需注册自己的 URL Scheme,并在 AppDelegate 或 SceneDelegate 中处理回调:
func application( _ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:] ) -> Bool { guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false) else { return false } let params = Dictionary(uniqueKeysWithValues: (components.queryItems ?? []).compactMap { item in item.value.map { (item.name, $0) } } ) if url.host == "bindresult" { if let deviceId = params["anonymous_device_id"] { UserDefaults.standard.set(deviceId, forKey: "bound_device_id") } else if let error = params["error"] { print("绑定失败: \(error)") } return true } return false }
2.7 Android 调用与回调接收
Android 第三方 App 使用 Intent 拉起 UU 远程。这里的 Android 指第三方调用端平台,不表示支持绑定 Android 被控设备。
val callback = "myapp://bindresult" val nonce = UUID.randomUUID().toString() val url = "uuremote://external/device/authorize" + "?callback=${URLEncoder.encode(callback, "UTF-8")}" + "&nonce=$nonce" startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
第三方 App 需注册自己的 scheme,并在 Activity 中读取 intent.data 的 anonymous_device_id、nonce 或 error 参数。
3. 发起远控
3.1 触发方式
第三方 App 通过以下 URL Scheme 发起远控连接:
uuremote://external/device/control?anonymous_device_id=<加密设备ID>&window_type=<窗口模式>[&callback=<回调URL>]
本接口仅支持通过设备绑定流程获得的 Windows/macOS 设备标识。
3.2 请求参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
anonymous_device_id |
String | 是 | 绑定流程返回的加密设备标识。参数值需进行 URL 编码。 |
window_type |
Int | 否 | 窗口模式:0 = 全屏,1 = 画中画小窗;默认值为 0。 |
callback |
String | 否 | 第三方 App 的回调 URL。仅小窗模式且传入该参数时,UU 远程才会通过该 URL 回调最终结果;不传不影响发起远控。全屏模式不回调第三方 App。参数值需进行 URL 编码。 |
3.3 窗口模式说明
全屏模式(window_type=0)
- UU 远程以全屏方式展示远控画面。
- 远端首帧成功显示后,本次请求判定为成功。
- 无论远控成功或失败,全屏模式都不通过
callback回调第三方 App。
小窗模式(window_type=1)
- UU 远程在连接成功后尝试进入画中画(PiP)小窗模式。
- 只有 PiP 实际启动成功,本次请求才判定为成功;若传入
callback,UU 远程回调status=success。 - PiP 不可用或启动失败时不降级为成功;若传入
callback,UU 远程回调status=failure及对应reason。 - iOS 客户端使用小窗模式要求 iOS 18.0 或更高版本,且当前设备支持画中画;不满足要求时返回
pip_unsupported。 - 用户可点击小窗恢复到 UU 远程全屏查看。
3.4 调用示例
以下示例演示需要接收小窗远控结果时的调用方式;不需要回调时,可省略 callback 参数。
iOS
let deviceId = UserDefaults.standard.string(forKey: "bound_device_id") ?? "" let callback = "myapp://controlresult" let windowType = 1 let encodedDeviceId = deviceId.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? deviceId let encodedCallback = callback.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? callback let urlString = "uuremote://external/device/control?anonymous_device_id=\(encodedDeviceId)&window_type=\(windowType)&callback=\(encodedCallback)" if let url = URL(string: urlString) { UIApplication.shared.open(url) }
Android
Android 第三方 App 使用 Intent 发起请求。这里的 Android 指第三方调用端平台,目标设备仍仅支持 Windows/macOS。
val deviceId = URLEncoder.encode(savedDeviceId, "UTF-8") val callback = URLEncoder.encode("myapp://controlresult", "UTF-8") val url = "uuremote://external/device/control" + "?anonymous_device_id=$deviceId&window_type=1&callback=$callback" startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
3.5 回调参数
callback 为可选参数。仅小窗模式且请求中传入有效的 callback 时,UU 远程才会回调第三方 App;未传入 callback 时不回调,也不影响远控流程。全屏模式无论是否传入 callback、远控成功或失败都不回调。小窗模式发生回调时,UU 远程在第三方传入的 callback URL 上追加结果参数。若 callback 已包含查询参数,UU 远程会保留原参数,并追加或覆盖同名的结果字段;成功回调时会同时移除 callback 中原有的 reason,避免第三方读到上一次的失败原因。
小窗模式连接成功
<callback>?status=success
小窗模式连接失败
<callback>?status=failure&reason=<失败原因>
| 参数 | 说明 |
|---|---|
status |
小窗远控的最终状态:success 或 failure。 |
reason |
失败原因。仅在 status=failure 时返回;成功时不返回。 |
reason 取值
reason |
含义 | 第三方 App 建议处理 |
|---|---|---|
busy |
UU 远程正在远控其他设备,或当前处于不允许发起远控的互斥业务流程 | 提示用户先在 UU 远程结束当前远控或互斥业务后重试 |
device_offline |
目标设备存在,但当前离线 | 提示用户开启或唤醒目标设备后重试 |
device_not_found |
设备不存在、已解绑,或切换 UU 账号导致 anonymous_device_id 失效 |
清除旧设备标识并引导用户重新绑定 |
pip_unsupported |
请求小窗,但当前系统、设备或运行状态不支持 PiP | 提示无法使用小窗,可改用全屏模式重试 |
login_timeout |
用户未在规定时间内完成 UU 远程登录,或首页初始化超时 | 结束等待并允许用户重新发起 |
network_unavailable |
本机无可用网络,或请求明确因断网、DNS、网络不可达而失败 | 提示用户检查本机网络后重试 |
not_allowed |
设备未开启允许被控,或目标设备平台不受支持 | 引导用户检查被控端设置和设备平台 |
connect_timeout |
建连、等待首帧或启动 PiP 超时 | 提示用户重试 |
connect_failed |
无法进一步区分的连接失败 | 展示通用连接失败提示并允许重试 |
user_cancel |
用户取消登录或主动终止本次远控流程 | 结束等待 |
invalid_request |
必填参数缺失或参数值不合法 | 检查请求参数 |
internal_error |
UU 远程内部状态异常,无法继续处理请求 | 展示通用失败提示并允许重试 |
3.6 回调约定
- 回调仅用于传入有效
callback的小窗模式;status=success与status=failure互斥,成功时不返回reason,失败时必须返回非空的reason。 - 不再使用旧版
success=0/1字段。 - 全屏模式无论远控成功、失败、用户取消或超时都不回调第三方 App。
- 小窗模式传入有效
callback时,在成功、失败、用户取消或超时后最终且只回调一次;未传入时不回调。 - 小窗模式传入有效
callback时,前置拦截类失败(设备不允许被控、设备不存在、设备离线、当前忙碌)会先在 UU 远程内弹窗提示用户,用户点击确认后才回调对应的reason;该路径不启用本地兜底超时,第三方 App 需按用户确认时机处理,不应据此判定回调丢失。若提示弹窗无法展示,UU 远程立即以对应reason回调。 - 小窗模式传入有效
callback时,无需用户确认的失败(参数非法、PiP 启动失败等)会立即回调。 - 全屏远控以远端首帧成功显示为成功,但不回调结果;小窗远控以 PiP 实际启动成功为成功,并在传入有效
callback时回调status=success。 - 第三方 App 收到未知
reason时,应按connect_failed处理,不应忽略回调。 - 第三方 App 不应在全屏模式或未传入
callback时等待回调。传入有效callback的小窗模式可保留本地总超时作为异常容灾,但不应依赖它代替正常失败回调;前置拦截路径的等待时长取决于用户在 UU 远程内的确认操作,本地超时应留出足够余量。
3.7 iOS 回调接收示例
以下示例处理小窗模式传入 callback 后收到的成功和失败回调。全屏模式不会进入该回调处理。
if url.host == "controlresult" { switch params["status"] { case "success": print("远控连接成功") case "failure": let reason = params["reason"] ?? "connect_failed" if reason == "device_not_found" { UserDefaults.standard.removeObject(forKey: "bound_device_id") // 引导用户重新执行设备绑定 } else { print("远控连接失败: \(reason)") } default: break } }
4. 异常和错误处理
4.1 通用异常
device/control 的回调结果仅适用于传入有效 callback 的小窗模式。全屏模式的异常由 UU 远程内部展示和处理,不回调第三方 App。
| 场景 | 表现 | 建议处理 |
|---|---|---|
| iOS 未安装 UU 远程 | canOpenURL 返回 false |
引导用户前往 App Store 安装 |
| Android 未安装 UU 远程 | Intent 无法解析 | 按接入方既有分发渠道引导用户安装 |
device/control 携带非法 callback |
请求参数非法,无法回调 | 如需回调,发起请求前校验并正确编码 callback;无需回调时不要传入该参数 |
device/authorize 缺少或携带非法 callback |
同上,绑定流程不执行且无法回调 | 发起请求前校验并正确编码 callback |
device/control 缺少 anonymous_device_id |
status=failure&reason=invalid_request |
确保必填参数完整 |
window_type 不是 0 或 1 |
status=failure&reason=invalid_request |
修正窗口模式参数 |
4.2 绑定流程异常
| 场景 | 回调 | 说明 |
|---|---|---|
| 未登录或首页未就绪,排队等待超时 | error=timeout |
排队窗口 180 秒,结束等待并允许用户重试 |
| 设备列表加载超时 | error=timeout |
15 秒窗口内未拿到设备列表 |
| 排队被触发时已不满足登录态或首页条件 | error=not_logined |
结束等待,用户需重新发起绑定 |
| 绑定请求被新的同类请求取代,或同批未被选中 | error=superseded |
同一时刻仅处理最新一次请求,被取代的请求也会收到一次失败回调 |
| 排队期间用户退出登录 | error=logout |
请求随登录态失效被清理 |
| 过滤后没有 Windows/macOS 设备 | error=no_device |
Android、iOS、TV 等设备不计入可关联设备;须用户点击提示弹窗确认后才回调 |
| 用户主动取消 | error=user_cancel |
用户点击关闭按钮、下拉关闭,或绑定页面被关闭 |
| UU 远程内部异常 | error=internal_error |
页面栈缺失、无可用展示宿主、设备标识加密失败等统一归入此类,不暴露内部细节 |
4.3 远控连接异常
下表中的回调仅适用于传入有效 callback 的小窗模式。未传入 callback 或全屏模式遇到相同异常时不回调第三方 App。
| 场景 | 回调 | 建议处理 |
|---|---|---|
| UU 远程正在远控其他设备 | status=failure&reason=busy |
引导用户先结束当前远控 |
| 目标设备离线 | status=failure&reason=device_offline |
引导用户开启或唤醒设备 |
| 设备标识对应旧 UU 账号,或设备已解绑/不存在 | status=failure&reason=device_not_found |
清除旧标识并引导用户重新绑定 |
| 本机网络不可用 | status=failure&reason=network_unavailable |
检查网络后重试 |
| 连接或首帧等待超时 | status=failure&reason=connect_timeout |
结束等待并允许用户重试 |
| 连接失败但无法进一步分类 | status=failure&reason=connect_failed |
展示通用连接失败提示 |
| PiP 不可用 | status=failure&reason=pip_unsupported |
改用全屏模式重试 |
| 登录或首页初始化超时 | status=failure&reason=login_timeout |
结束等待并允许用户重新发起 |
| 设备不允许被控,或目标平台不受支持 | status=failure&reason=not_allowed |
引导用户检查被控端允许被控设置与设备平台;须用户点击提示弹窗确认后才回调 |
| 用户取消登录或主动终止本次远控 | status=failure&reason=user_cancel |
结束等待 |
| 必填参数缺失或取值非法 | status=failure&reason=invalid_request |
修正请求参数后重试 |
| UU 远程内部状态异常 | status=failure&reason=internal_error |
展示通用失败提示并允许重试 |
4.4 超时机制
远控超时仅在传入有效 callback 的小窗模式下回调第三方 App;未传入 callback 或全屏模式不回调。设备绑定流程的超时回调不受此规则影响。
| 超时项 | 时长 | 回调 |
|---|---|---|
| 登录/首页初始化等待 | 180 秒 | status=failure&reason=login_timeout |
| 首页设备列表等待 | 15 秒 | status=failure&reason=connect_failed |
| 远控连接与首帧等待(本地兜底,前置拦截弹窗路径除外) | 30 秒 | status=failure&reason=connect_timeout |
| Scene 激活或 PiP 启动等待 | 由 UU 远程内部控制 | connect_timeout、pip_unsupported 或对应连接错误 |
| 绑定排队等待(未登录 / 首页未就绪) | 180 秒 | error=timeout |
| 绑定设备列表等待 | 15 秒 | error=timeout |
| 绑定「当前无可关联设备」提示弹窗 | 无本地超时 | 用户点击提示后回调 error=no_device |
| 远控前置拦截提示弹窗(忙碌、设备不存在/离线、不允许被控) | 无本地超时 | 用户点击确认后回调对应的 status=failure&reason=busy/device_not_found/device_offline/not_allowed |
4.5 注意事项
- 目标设备范围:仅支持绑定和远控 Windows/macOS 设备,不支持 Android、iOS、TV 等其他平台的被控设备。
- 账号绑定关系:
anonymous_device_id与生成该标识时登录的 UU 账号绑定。切换 UU 账号后,旧标识会返回device_not_found。 - 回调 URL 编码:传入
callback时,callback和anonymous_device_id可能包含://、+、=等 URL 特殊字符,必须进行 URL 编码后再拼接。 - 回调原参数:
device/control(以及device/authorize)发生回调时会保留callback原有查询参数,并追加本次结果;同名的结果字段以本次结果为准,且小窗模式远控成功时会移除原有reason。 - 重复绑定:同一第三方 App 可多次调用绑定接口。第三方 App 应保存最新的
anonymous_device_id。被后发请求取代的绑定请求会收到error=superseded。 - 并发请求:请勿同时发起多个绑定或远控请求。UU 远程同一时刻仅处理一个第三方请求;传入有效
callback的小窗远控请求被替换或取消时会收到一次失败回调,未传入callback的小窗请求和全屏远控请求不回调;绑定请求被取代或落选时回调error=superseded。 - 错误码粒度:
internal_error是端内异常的兜底错误码,具体内部原因(加密失败、页面栈缺失、无可用展示宿主等)不会透出给第三方,第三方只需按通用失败处理。
附录:协议速查
绑定协议
-> uuremote://external/device/authorize?callback={url}&nonce={uuid}
<- {callback}?anonymous_device_id={encrypted_id}&nonce={uuid}
<- {callback}?error={error_code}&nonce={uuid}
{error_code} 取值:no_device、user_cancel、timeout、not_logined、superseded、logout、internal_error(详见 2.5)。
绑定目标设备:仅 Windows/macOS。
远控协议
-> uuremote://external/device/control?anonymous_device_id={encrypted_id}&window_type={0|1}[&callback={url}]
window_type=0(全屏):
<- 不回调
window_type=1(小窗,传入 callback 时):
<- {callback}?status=success
<- {callback}?status=failure&reason={reason}
window_type=1(小窗,未传入 callback 时):
<- 不回调
远控目标设备:仅 Windows/macOS。