使用Compose Multiplatform开发跨平台Android调试工具
使用Compose Multiplatform开发跨平台Android调试工具
本文详细介绍了使用Compose Multiplatform开发跨平台Android调试工具DebugManager的过程。文章从背景、架构设计、Gradle配置、多平台适配、功能实现等多个维度,深入探讨了跨平台开发的具体实践,对于有志于进行跨平台开发的开发者具有很高的参考价值。
背景
作者对Compose Multiplatform(CMP)跨平台技术表现出浓厚兴趣,并通过开发一个支持Android和iOS的天气应用进行了初步实践。在掌握了移动端开发后,作者了解到CMP不仅限于移动端,还可以扩展到Web和Desktop端。基于此,作者决定开发一个带界面的Android设备调试工具DebugManager,以进一步探索Desktop端的开发能力。
架构设计
由于缺乏Desktop端开发经验,作者选择采用Google推崇的MVI(Model-View-Intent)模式。考虑到功能单一且主要涉及命令行操作,数据层采用单例类设计,通过adb工具获取数据并传递给StateHolder。StateHolder负责管理界面状态,通过StateFlow监听数据变化并触发UI更新。
事件从上到下传递,数据状态从下到上流动,确保数据流的唯一性和可靠性。
Gradle配置
DebugManager的目标是实现多端通用,支持Windows、Linux和MacOS系统。Gradle配置决定了软件的平台兼容性和安装包特性。以下是Windows端的部分配置示例:
menu = true
shortcut = true
// 可自行选择安装目录
dirChooser = true
// 可单独为当前用户安装,不需要管理员权限
perUserInstall = true
// 设置图标
iconFile.set(project.file("launcher/icon.ico"))
upgradeUuid = "xxxx-xxxxxxx-xxxxx"
详细的Gradle属性配置可参考官方GitHub仓库的教程文档。
Multiplatform适配
属性配置
Desktop跨平台开发面临的一个挑战是不同平台的路径连接符差异。作者通过System.getProperty
方法区分平台类型,并定义了一个枚举类来表示不同平台:
enum class PlatformType {
UNKNOWN,
WINDOWS,
MAC,
LINUX,
}
在应用初始化时,通过解析系统属性来确定当前平台:
private fun getPlatformType(): PlatformType {
val osName = System.getProperty("os.name").lowercase(Locale.getDefault())
return when {
osName.contains("win") -> PlatformType.WINDOWS
osName.contains("mac") -> PlatformType.MAC
osName.contains("nix") || osName.contains("nux") || osName.contains("aix") -> PlatformType.LINUX
else -> PlatformType.UNKNOWN
}
}
在涉及平台差异化处理时,可以调用getPlatformType
方法执行不同操作。例如,路径拼接时的符号处理:
val dp = when (getPlatformType()) {
PlatformType.WINDOWS, PlatformType.UNKNOWN -> "\\"
PlatformType.MAC, PlatformType.LINUX -> "/"
}
打开不同平台上的文件管理器:
fun openFolder(path: String) {
when (getPlatformType()) {
PlatformType.WINDOWS, PlatformType.UNKNOWN -> {
executeTerminalCommand("explorer.exe $path")
}
PlatformType.MAC -> {
executeTerminalCommand("open $path")
}
PlatformType.LINUX -> {
executeTerminalCommand("xdg-open $path")
}
}
}
对于终端命令的执行,统一使用两种方法:无需结果时使用exec()
,需要结果时使用ProcessBuilder
。
窗口框架
新项目的应用入口如下:
fun main() = application {
Window(
onCloseRequest = {
},
title = "DebugManager",
undecorated = true,
state = windowState,
icon = painterResource("image/icon.png"),
) {
....
}
}
通过windowState
可以设置窗口初始大小和最大最小化。undecorated
参数用于选择是否使用系统默认标题栏。为了实现自定义标题栏的拖动功能,作者使用了WindowDraggableArea
组件。
功能划分
设备信息展示
首页展示所连接设备的基本信息。定义了DeviceState
数据类来存储所需展示的字段:
data class DeviceState(
val name: String? = null,
val manufacturer: String? = null,
val sdkVersion: String? = null,
val systemVersion: String? = null,
val buildType: String? = null,
val innerName: String? = null,
val resolution: String? = null,
val density: String? = null,
val cpuArch: String? = null,
val serial:String? = null,
val isConnected: Boolean = false
) {
fun toUiState() =
DeviceState(
name = name,
systemVersion = systemVersion,
manufacturer = manufacturer,
sdkVersion = sdkVersion,
buildType = buildType,
innerName = innerName,
resolution = resolution,
cpuArch = cpuArch,
density = density,
serial = serial,
isConnected = isConnected
)
}
在StateHolder中维护一个StateFlow,并在界面层暴露只读字段用于刷新数据:
private val _deviceState = MutableStateFlow(DeviceState())
val deviceStateStateFlow = _deviceState.asStateFlow()
通过协程获取数据并更新界面:
CoroutineScope(Dispatchers.IO).launch {
prepareEnv()
val deviceName = .....
_deviceState.update {
it.copy(
name = deviceName,
manufacturer = manufacturer,
sdkVersion = sdkVersion,
systemVersion = systemVersion,
buildType = buildType,
density = displayDensity,
innerName = innerName,
resolution = displayResolution,
cpuArch = architecture,
serial = serialNum
)
}
_deviceState.value = _deviceState.value.toUiState()
// 初始化获取文件列表
getFileList()
}
右侧按钮提供了高频使用的功能,如reboot、root等。其中执行qnx命令为车机特有,可以桥接到QNX系统执行底层命令。录屏、截屏功能支持自动导出到电脑分享。
软件安装管理
该功能模块耗时最长,涉及Android系统包信息展示和管理。支持全量和三方包扫描,提供覆盖安装、测试安装等选项。应用图标通过在Android端开发的服务app预先扫描并存储,再通过adb pull拉取到电脑端显示。
文件管理器
文件管理器通过执行ls
命令获取目录列表,支持双击和单击区分操作。文件操作通过命令行形式实现,支持cp、mv、rm等命令,以及文件的pull和push操作。
命令模式
命令模式页面包含一个Compose原生的TextField输入框,支持基础adb命令透传,配合系统厂商的可执行二进制程序,可以模拟车载信号回调操作。
关于页
关于页显示软件版本和缓存文件目录等信息,通过PlatformAdapter工具类获取路径并打开界面。
开源计划
DebugManager最初基于公司业务开发,包含部分公司内部信息。作者计划在适当时候将其功能进行删减,改造成通用性质的Android调试工具,并开源到GitHub。对CMP跨平台感兴趣的开发者可以关注后续进展。