问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

使用Compose Multiplatform开发跨平台Android调试工具

创作时间:
作者:
@小白创作中心

使用Compose Multiplatform开发跨平台Android调试工具

引用
CSDN
1.
https://m.blog.csdn.net/weixin_43240212/article/details/144078853

本文详细介绍了使用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跨平台感兴趣的开发者可以关注后续进展。

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号