android-sunflower中的Compose与USB设备:USB Host模式

【免费下载链接】sunflower A gardening app illustrating Android development best practices with migrating a View-based app to Jetpack Compose. 【免费下载链接】sunflower 项目地址: https://gitcode.com/gh_mirrors/an/android-sunflower

你是否在开发Android应用时遇到过需要连接USB设备的场景?是否想知道如何在基于Jetpack Compose的应用中实现USB Host模式功能?本文将以android-sunflower项目为例,详细介绍如何在Compose架构中集成USB设备通信功能,让你的应用轻松与外部硬件交互。

读完本文后,你将能够:

  • 了解Android USB Host模式的基本概念和工作原理
  • 掌握在Jetpack Compose中检测和连接USB设备的方法
  • 学会在MVVM架构中设计USB设备通信模块
  • 实现Compose界面与USB设备的数据交互

USB Host模式简介

USB Host模式允许Android设备充当USB主机,为连接的USB设备提供电力并与之通信。这在需要与外部硬件设备交互的应用中非常有用,比如园艺监测设备、传感器等。

在AndroidManifest.xml中声明USB Host权限是使用该功能的第一步:

<uses-permission android:name="android.hardware.usb.host" />
<uses-feature android:name="android.hardware.usb.host" android:required="true" />

android-sunflower项目的清单文件位于app/src/main/AndroidManifest.xml,你需要在这里添加上述权限声明。

项目架构概览

android-sunflower项目展示了如何将基于View的应用迁移到Jetpack Compose。其架构遵循MVVM模式,主要分为以下几个模块:

项目架构

USB设备检测与权限请求

要在Compose应用中使用USB设备,首先需要检测连接的USB设备并请求用户授权。以下是实现这一功能的关键步骤:

  1. 创建一个USB设备接收器,监听USB设备的连接和断开事件:
class UsbDeviceReceiver(
    private val onDeviceConnected: (UsbDevice) -> Unit,
    private val onDeviceDisconnected: (UsbDevice) -> Unit
) : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        when (intent.action) {
            UsbManager.ACTION_USB_DEVICE_ATTACHED -> {
                val device = intent.getParcelableExtra<UsbDevice>(UsbManager.EXTRA_DEVICE)
                device?.let { onDeviceConnected(it) }
            }
            UsbManager.ACTION_USB_DEVICE_DETACHED -> {
                val device = intent.getParcelableExtra<UsbDevice>(UsbManager.EXTRA_DEVICE)
                device?.let { onDeviceDisconnected(it) }
            }
        }
    }
}
  1. 在ViewModel中注册接收器并管理USB设备列表:
class UsbViewModel(private val usbManager: UsbManager) : ViewModel() {
    private val _connectedDevices = MutableStateFlow<List<UsbDevice>>(emptyList())
    val connectedDevices: StateFlow<List<UsbDevice>> = _connectedDevices
    
    private lateinit var usbReceiver: UsbDeviceReceiver
    
    fun initialize(context: Context) {
        usbReceiver = UsbDeviceReceiver(
            onDeviceConnected = { device ->
                _connectedDevices.update { it + device }
                requestPermission(context, device)
            },
            onDeviceDisconnected = { device ->
                _connectedDevices.update { it - device }
            }
        )
        
        val filter = IntentFilter().apply {
            addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED)
            addAction(UsbManager.ACTION_USB_DEVICE_DETACHED)
        }
        context.registerReceiver(usbReceiver, filter)
    }
    
    private fun requestPermission(context: Context, device: UsbDevice) {
        val permissionIntent = PendingIntent.getBroadcast(
            context, 0, Intent(ACTION_USB_PERMISSION), 
            PendingIntent.FLAG_IMMUTABLE
        )
        usbManager.requestPermission(device, permissionIntent)
    }
    
    override fun onCleared() {
        super.onCleared()
        context.unregisterReceiver(usbReceiver)
    }
    
    companion object {
        const val ACTION_USB_PERMISSION = "com.google.samples.apps.sunflower.USB_PERMISSION"
    }
}

Compose界面集成USB功能

在android-sunflower项目中,我们可以在现有的Compose界面中添加USB设备管理功能。例如,在app/src/main/java/com/google/samples/apps/sunflower/compose/garden/GardenScreen.kt中添加一个USB设备状态显示区域:

@Composable
fun GardenScreen(
    viewModel: GardenPlantingListViewModel,
    navigateToPlantDetail: (String) -> Unit,
    usbViewModel: UsbViewModel = viewModel()
) {
    val gardenPlantings by viewModel.gardenPlantings.observeAsState(emptyList())
    val connectedDevices by usbViewModel.connectedDevices.collectAsState()
    
    Scaffold(
        topBar = {
            TopAppBar(
                title = { Text(stringResource(id = R.string.my_garden)) },
                actions = {
                    UsbDeviceStatusIcon(connectedDevices.isNotEmpty())
                }
            )
        }
    ) { innerPadding ->
        val modifier = Modifier.padding(innerPadding)
        
        if (connectedDevices.isNotEmpty()) {
            UsbDeviceList(
                devices = connectedDevices,
                onDeviceSelected = { device -> 
                    // 处理设备选择
                }
            )
        }
        
        GardenContent(
            gardenPlantings = gardenPlantings,
            onAddPlantClick = { navigateToPlantDetail("") },
            onPlantClick = { plant -> navigateToPlantDetail(plant.plantId) },
            modifier = modifier
        )
    }
}

@Composable
fun UsbDeviceStatusIcon(isConnected: Boolean) {
    Icon(
        imageVector = if (isConnected) Icons.Filled.Usb else Icons.Outlined.Usb,
        contentDescription = stringResource(R.string.usb_status),
        tint = if (isConnected) Color.Green else Color.Gray
    )
}

@Composable
fun UsbDeviceList(
    devices: List<UsbDevice>,
    onDeviceSelected: (UsbDevice) -> Unit
) {
    Column(
        modifier = Modifier
            .fillMaxWidth()
            .background(Color.LightGray.copy(alpha = 0.5f))
            .padding(8.dp)
    ) {
        Text(
            text = stringResource(R.string.connected_usb_devices),
            style = MaterialTheme.typography.subtitle1,
            modifier = Modifier.padding(bottom = 4.dp)
        )
        devices.forEach { device ->
            UsbDeviceItem(device, onDeviceSelected)
        }
    }
}

@Composable
fun UsbDeviceItem(
    device: UsbDevice,
    onDeviceSelected: (UsbDevice) -> Unit
) {
    TextButton(onClick = { onDeviceSelected(device) }) {
        Text(
            text = "USB Device: ${device.deviceName}",
            modifier = Modifier.fillMaxWidth()
        )
    }
}

USB设备列表界面

USB数据通信实现

连接USB设备后,下一步是实现数据通信。我们可以创建一个UsbCommunicator类来处理与USB设备的数据交换:

class UsbCommunicator(
    private val usbManager: UsbManager,
    private val device: UsbDevice
) {
    private var connection: UsbDeviceConnection? = null
    private var endpoint: UsbEndpoint? = null
    
    fun connect(): Boolean {
        val interfaceCount = device.interfaceCount
        for (i in 0 until interfaceCount) {
            val usbInterface = device.getInterface(i)
            for (j in 0 until usbInterface.endpointCount) {
                val ep = usbInterface.getEndpoint(j)
                if (ep.type == UsbConstants.USB_ENDPOINT_XFER_BULK) {
                    if (ep.direction == UsbConstants.USB_DIR_IN) {
                        endpoint = ep
                        break
                    }
                }
            }
            
            if (endpoint != null) {
                connection = usbManager.openDevice(device)
                if (connection != null) {
                    connection?.claimInterface(usbInterface, true)
                    return true
                }
            }
        }
        return false
    }
    
    fun readData(): ByteArray? {
        endpoint?.let { ep ->
            val buffer = ByteArray(ep.maxPacketSize)
            val bytesRead = connection?.bulkTransfer(ep, buffer, buffer.size, 1000)
            return if (bytesRead > 0) buffer.copyOf(bytesRead) else null
        }
        return null
    }
    
    fun writeData(data: ByteArray): Int {
        endpoint?.let { ep ->
            val outEndpoint = ep
            return connection?.bulkTransfer(outEndpoint, data, data.size, 1000) ?: -1
        }
        return -1
    }
    
    fun disconnect() {
        connection?.close()
    }
}

完整的USB通信流程

下面是在android-sunflower项目中实现USB Host功能的完整流程:

  1. 在AndroidManifest.xml中添加USB Host权限和特性声明:
<uses-permission android:name="android.hardware.usb.host" />
<uses-feature android:name="android.hardware.usb.host" />

<activity
    android:name=".GardenActivity"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
    <intent-filter>
        <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
    </intent-filter>
    <meta-data
        android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
        android:resource="@xml/usb_device_filter" />
</activity>
  1. 创建USB设备过滤器xml/usb_device_filter.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <usb-device vendor-id="1234" product-id="5678" />
</resources>
  1. 在Application类中初始化USB相关组件:
class MainApplication : Application() {
    lateinit var usbManager: UsbManager
    
    override fun onCreate() {
        super.onCreate()
        usbManager = getSystemService(Context.USB_SERVICE) as UsbManager
    }
}
  1. 在ViewModel中集成USB通信逻辑:
class PlantDetailViewModel(
    plantRepository: PlantRepository,
    private val usbManager: UsbManager,
    savedStateHandle: SavedStateHandle
) : ViewModel() {
    private val plantId: String = savedStateHandle.get<String>(PLANT_ID_ARG)!!
    val plant: LiveData<Plant?> = plantRepository.getPlant(plantId)
    
    private var usbCommunicator: UsbCommunicator? = null
    private val _usbData = MutableStateFlow<ByteArray?>(null)
    val usbData: StateFlow<ByteArray?> = _usbData
    
    fun connectToDevice(device: UsbDevice) {
        if (usbCommunicator?.isConnected != true) {
            usbCommunicator = UsbCommunicator(usbManager, device).apply {
                if (connect()) {
                    startDataListening()
                }
            }
        }
    }
    
    private fun startDataListening() {
        viewModelScope.launch(Dispatchers.IO) {
            while (isActive) {
                val data = usbCommunicator?.readData()
                data?.let {
                    _usbData.value = it
                }
                delay(100)
            }
        }
    }
    
    fun sendDataToDevice(data: ByteArray) {
        viewModelScope.launch(Dispatchers.IO) {
            usbCommunicator?.writeData(data)
        }
    }
    
    override fun onCleared() {
        super.onCleared()
        usbCommunicator?.disconnect()
    }
    
    companion object {
        const val PLANT_ID_ARG = "plantId"
    }
}
  1. 在植物详情界面添加USB数据显示和控制功能:
@Composable
fun PlantDetailView(
    plant: Plant,
    viewModel: PlantDetailViewModel,
    modifier: Modifier = Modifier
) {
    val usbData by viewModel.usbData.collectAsState()
    
    ScrollableColumn(modifier = modifier) {
        PlantHeader(plant)
        PlantDetails(plant)
        
        if (usbData != null) {
            UsbDataDisplay(data = usbData!!)
        }
        
        UsbControlPanel(
            onSendCommand = { command ->
                viewModel.sendDataToDevice(command.toByteArray())
            }
        )
    }
}

@Composable
fun UsbDataDisplay(data: ByteArray) {
    Card(
        modifier = Modifier
            .fillMaxWidth()
            .padding(16.dp),
        elevation = 4.dp
    ) {
        Column(modifier = Modifier.padding(16.dp)) {
            Text(
                text = stringResource(R.string.usb_data_received),
                style = MaterialTheme.typography.subtitle1,
                modifier = Modifier.padding(bottom = 8.dp)
            )
            Text(
                text = data.toString(Charsets.UTF_8),
                style = MaterialTheme.typography.body1,
                backgroundColor = Color.Black,
                color = Color.Green,
                fontFamily = FontFamily.Monospace,
                modifier = Modifier.padding(8.dp)
            )
        }
    }
}

@Composable
fun UsbControlPanel(onSendCommand: (String) -> Unit) {
    var commandText by remember { mutableStateOf("") }
    
    Card(
        modifier = Modifier
            .fillMaxWidth()
            .padding(16.dp),
        elevation = 4.dp
    ) {
        Column(modifier = Modifier.padding(16.dp)) {
            Text(
                text = stringResource(R.string.usb_control_panel),
                style = MaterialTheme.typography.subtitle1,
                modifier = Modifier.padding(bottom = 8.dp)
            )
            OutlinedTextField(
                value = commandText,
                onValueChange = { commandText = it },
                label = { Text(stringResource(R.string.command)) },
                modifier = Modifier.fillMaxWidth()
            )
            Button(
                onClick = { onSendCommand(commandText) },
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(top = 8.dp)
            ) {
                Text(stringResource(R.string.send_command))
            }
        }
    }
}

植物详情与USB控制界面

总结与展望

通过本文的介绍,我们了解了如何在android-sunflower项目中集成USB Host模式功能。从设备检测、权限请求到数据通信,我们一步步实现了完整的USB设备交互流程,并将其与Jetpack Compose架构无缝结合。

关键要点回顾:

  • USB Host模式允许Android设备作为主机与外部USB设备通信
  • 在Compose中可以通过State和Flow实现USB设备状态的响应式更新
  • MVVM架构为USB通信功能提供了清晰的模块划分
  • 使用ViewModel管理USB连接和数据通信逻辑,确保配置变更时的状态保持

未来可以进一步探索的方向:

  • 实现更复杂的USB设备通信协议
  • 添加USB设备连接状态的通知功能
  • 优化USB数据传输的性能和稳定性
  • 支持多USB设备同时连接和通信

希望本文能帮助你在自己的Jetpack Compose项目中顺利实现USB Host功能。如果你有任何问题或建议,欢迎在评论区留言讨论。

点赞、收藏、关注三连,获取更多Android开发实用技巧!下期我们将介绍如何在android-sunflower中实现蓝牙设备通信功能,敬请期待。

【免费下载链接】sunflower A gardening app illustrating Android development best practices with migrating a View-based app to Jetpack Compose. 【免费下载链接】sunflower 项目地址: https://gitcode.com/gh_mirrors/an/android-sunflower

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐