Privilege UI
priv-ui 是接入 Priv Kit 时推荐的起点。它通过 Compose 界面展示运行时授权状态、 提供启动入口,并在静默启动时复用最近一次成功的前台启动方式。自定义界面可以直接 使用 priv-core。
常用 API
PrivilegeScaffold提供可嵌入 Compose 页面。PrivilegeUiViewModel是可继承的AndroidViewModel控制器。PrivilegeUiConfig用于启用启动方式和外部 Provider。PrivilegeUiExternalStartProvider用于添加应用提供的外部启动方式。PrivilegeUi.desiredEnabled以只读进程级StateFlow<Boolean>公开持久化的自动恢复 意图。PrivilegeUi.startSilently(...)在自动恢复开启时按上次成功的方式静默启动。ignoreAutomaticRecoverySetting = true会显式忽略该设置。
复用进程级配置
在进程级顶层属性中只创建一次外部 Provider 和 PrivilegeUiConfig,然后把同一个 实例用于前台 ViewModel 和静默启动 API:
val privilegeUiConfig by lazy {
PrivilegeUiConfig(
startupModes = listOf(
PrivilegeUiStartupMode.ROOT,
PrivilegeUiStartupMode.ADB,
PrivilegeUiStartupMode.MANUAL_SHELL,
),
externalStartProviders = listOf(shizukuProvider),
)
}
val serverInfo = PrivilegeUi.startSilently(
config = privilegeUiConfig,
)顶层属性及其外部 Provider 使用进程安全的 Application 状态,不持有 Activity。 Provider ID 用于保存启动方式,应用升级后保持不变。
startupModes 是控制授权 Tab 顺序的有序列表,传入重复项会直接报错。存在外部 Provider 时,EXTERNAL 会保留列表中的位置;如果列表中没有它,则自动追加到 末尾。没有外部 Provider 时,即使列表包含 EXTERNAL 也不会显示 External Tab。
嵌入 Scaffold
class MyPrivilegeUiViewModel(
application: Application,
) : PrivilegeUiViewModel(
application,
privilegeUiConfig,
) {
override fun onBackClick(): Boolean {
return true
}
}
PrivilegeScaffold(
viewModel = viewModel<MyPrivilegeUiViewModel>(),
)Scaffold 会自己注册 Activity Result launcher,并把权限结果交给正在等待结果的 ViewModel。
Server 已连接时,点击内置的 Root、ADB 或外部启动按钮会先显示重新启动确认。 取消不会影响当前 server;继续后,所选启动身份会先结束旧进程,再启动替代进程。 如果该身份无权结束旧进程,本次启动会停止,Scaffold 通过 Snackbar 提示失败。 自定义界面需要监听 serverRestartConfirmation,展示自己的确认交互,然后调用 confirmServerRestart() 或 cancelServerRestart()。发起启动的协程会挂起等待该 结果,随后在原调用上下文中继续所选流程。
观察服务端状态
PrivilegeScaffold 已经在内部观察运行时并展示状态。应用的其他功能依赖连接时, 应直接观察进程级的 Privilege.serverState,不再通过 UI 专用回调接收状态。
无论 PrivilegeScaffold 页面当前是否显示,该状态都持续可用。 启动方式分别给出了应用级持续监听和页面级状态 展示的写法。
自定义界面需要展示用户是否仍期望自动恢复时,可以单独观察 PrivilegeUi.desiredEnabled:
val desiredEnabled by PrivilegeUi.desiredEnabled.collectAsStateWithLifecycle()这个只读状态在断连和静默启动失败后仍会保留。当前连接状态由 Privilege.serverState 提供。
ADB 界面流程
priv-core 负责配对、发现、授权、TCP/IP 和启动,priv-ui 通过 PrivilegeScaffold 展示这些操作及其状态。
通过 PrivilegeUiConfig 配置由 UI 管理的 ADB 流程:
val config = PrivilegeUiConfig(
tcpPort = PRIVILEGE_ADB_DEFAULT_TCP_PORT,
adbTcpPolicy = PrivilegeUiAdbTcpPolicy.PREFER_EXISTING,
enableManagedWirelessAdb = true,
)无线调试界面
选中 ADB 启动方式后,面板会定期检查无线调试和配对状态。它的前台流程 可以:
- 展示配对对话框,并通过
priv-core提交六位配对码; - 通过可选的通知配对流程接收配对码;
- 在系统要求时请求
ACCESS_LOCAL_NETWORK; - 已保存的 ADB 密钥完成配对后,通过无线调试启动。
通知配对服务是 priv-ui 的内部组件,应用通过 PrivilegeScaffold 使用它。无法使用 通知输入时,Scaffold 会保留前台配对 对话框,供用户与 Android 设置页配合分屏操作。权限警告会把“无通知继续”、设置中 授权后继续或取消返回给同一个挂起的配对协程。
宿主可以通过 PrivilegeUiConfig.notificationPairingChannelId 和 notificationPairingNotificationId 预留自己的通知命名空间,默认值分别是 priv_ui_adb_pairing 和 201。通知 ID 会连续占用两个值:配置值用于前台状态通知, 下一个值用于输入配对码。渠道 ID 保持稳定并专用于此流程;通知 ID 从 1 到 Int.MAX_VALUE - 1 中选择一个宿主尚未占用的值。priv-ui 会按需创建配置的渠道, 但不会删除它。
priv-core 可以在具备权限时临时打开和关闭无线调试。priv-ui 读取状态并把 对应配置传给 Core,由 Core 负责 Settings.Global 变更。
TCP/IP 界面
PrivilegeUiConfig.tcpPort 指定静态端口。 PrivilegeUiConfig.adbTcpPolicy 用于关闭 TCP/IP、优先使用现有静态端口,或在 无线调试完成配对后提供创建静态端口的入口。
通过界面调用 priv-core 执行 adb tcpip 时,内置 Scaffold 会先展示一次确认。 取消后 ADB 保持不变。自定义界面调用 PrivilegeUiViewModel.enableTcpMode() 或 startStaticTcpAdb() 时,监听 staticTcpSwitchConfirmation,展示自己的 警告,再调用 confirmStaticTcpSwitch() 或 cancelStaticTcpSwitch()。
UI 可以请求本机 ADB 密钥授权,并在系统返回结果后继续之前的启动操作。仅检查状态 不会修改 ADB 设置。
静默启动要求配对、权限、TCP 授权和静态端口已经就绪,否则返回 null,交由前台 流程继续处理。
静默启动
前台启动成功并建立 Binder 连接后,UI 会保存一个启动方式 ID:
rootadb-wirelessadb-tcpipexternal:<providerId>
静默启动只会尝试已保存的方式,不会切换到其他方式。已保存的方式、授权或其他启动 条件不可用时返回 null,权限请求、配对和外部授权仍需在前台界面中完成。
前台启动和静默启动通过进程内启动门互斥。多进程应用选择一个进程初始化并调用 Priv Kit 的启动 API。静默启动期间,内置界面会暂时禁用其他启动 入口,并在完成后刷新运行时状态。如果 Root 管理器保存的授权已经失效,它仍可能 展示自己的授权界面。
UI 发起的前台启动成功并收到匹配的初始连接后,才会开启自动恢复。UI 前台启动 之外的初始连接(包括执行复制的手动 Shell 命令)保持当前设置。用户确认停止或选择 “关闭自动恢复”时关闭;断连、服务端死亡和静默启动失败保留原设置。 PrivilegeUi.desiredEnabled 会独立于当前服务端连接状态发布这项持久化意图。 startSilently(...) 默认遵循自动恢复设置; ignoreAutomaticRecoverySetting = true 会显式忽略它。
添加 Shizuku
应用可以通过 PrivilegeUiStreamingExternalStartProvider 将 Shizuku 添加为 priv-ui 的外部启动方式。该实现需要:
- 通过
snapshot()返回 Shizuku 可用性和权限状态; - 在
requestAuthorization()中请求权限,并在回调后返回最终状态; - 在
start()中绑定 Shizuku UserService,并把收到的commandLine交给PrivilegeExternalStartup.runThroughBridge(...); - 保持 Provider ID 不变,使静默启动在应用升级后仍能找到它。
按照上面的示例,将 Provider 注册到 PrivilegeUiConfig.externalStartProviders。外部启动示例 说明 Shizuku UserService 的 AIDL、UserService 实现和绑定。sample 提供了完整的 Privilege UI Provider。
跨平台展示
PrivilegePreviewScaffold() 可在 Android、JVM 和 WasmJS 中展示同一套页面。由宿主提供 Material 3 主题,内存模拟数据驱动正常的状态、按钮和弹窗。该入口不初始化运行时,也不申请权限。Android 真实功能继续使用 PrivilegeScaffold。
启动、配对、外部授权及确认弹窗默认可交互。操作模拟成功,配对接受任意六位数字。手动页提供示例命令,顶部“启动服务”操作模拟执行该命令。复制按钮使用宿主剪贴板,但不会执行命令或修改系统;关闭宿主后丢弃本次数据。可在 UI 展示 中体验。
手动命令使用为 priv.kit.sample 随机生成的安装路径。设置 useLegacyPackaging = false 可展示通过 linker64 加载 APK 内原生库的命令,默认值 true 展示解压后的原生库路径。展示页在画布外提供该开关;切换格式会保留路径和模拟状态。
通过顶栏的「UI 展示」打开独立的 Vue 展示页。