You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

求可正常编译的Kotlin版Android USB-Serial收发示例

Kotlin实现Android OTG USB-Serial基础收发方案

前置配置

  • 清单文件声明
    首先在AndroidManifest.xml中添加OTG特性、USB相关广播配置:
<!-- 声明USB主机特性 -->
<uses-feature android:name="android.hardware.usb.host" android:required="false" />

<activity
    android:name=".MainActivity"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
    <!-- 响应USB设备插入事件 -->
    <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/device_filter" />
</activity>

在res目录下新建xml文件夹,创建device_filter.xml用于指定需要自动识别的设备,不知道设备VID/PID时可以留空,后续运行时枚举所有设备:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!-- 已知设备VID/PID时可按如下格式配置,插入后会自动弹出授权提示
    <usb-device vendor-id="xxxx" product-id="xxxx" />
    -->
</resources>
  • 依赖配置
    在模块级build.gradle.kts(或Groovy版本的build.gradle)中添加串口库依赖:
dependencies {
    implementation("com.github.mik3y:usb-serial-for-android:3.7.2")
}

核心实现代码

直接在MainActivity.kt中编写如下代码,实现最基础的设备枚举、授权、连接、数据收发逻辑:

import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.hardware.usb.UsbDevice
import android.hardware.usb.UsbManager
import android.os.Bundle
import android.widget.Button
import android.widget.EditText
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import com.hoho.android.usbserial.driver.UsbSerialPort
import com.hoho.android.usbserial.driver.UsbSerialProber
import com.hoho.android.usbserial.util.SerialInputOutputManager
import java.io.IOException
import java.util.concurrent.Executors

class MainActivity : AppCompatActivity() {
    private lateinit var usbManager: UsbManager
    private var serialPort: UsbSerialPort? = null
    private lateinit var ioManager: SerialInputOutputManager
    // 请将下方字符串替换为你自己的应用包名
    private val ACTION_USB_PERMISSION = "替换为你的应用包名.USB_PERMISSION"

    private val usbReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context?, intent: Intent?) {
            when (intent?.action) {
                ACTION_USB_PERMISSION -> {
                    val device = intent.getParcelableExtra<UsbDevice>(UsbManager.EXTRA_DEVICE)
                    val granted = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)
                    if (granted && device != null) connectDevice(device)
                }
                UsbManager.ACTION_USB_DEVICE_ATTACHED -> {
                    val device = intent.getParcelableExtra<UsbDevice>(UsbManager.EXTRA_DEVICE)
                    device?.let { requestPermission(it) }
                }
                UsbManager.ACTION_USB_DEVICE_DETACHED -> closeSerial()
            }
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        usbManager = getSystemService(Context.USB_SERVICE) as UsbManager

        // 注册USB相关广播
        val filter = IntentFilter().apply {
            addAction(ACTION_USB_PERMISSION)
            addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED)
            addAction(UsbManager.ACTION_USB_DEVICE_DETACHED)
        }
        registerReceiver(usbReceiver, filter)

        val inputEt = findViewById<EditText>(R.id.et_input)
        val sendBtn = findViewById<Button>(R.id.btn_send)
        val receiveTv = findViewById<TextView>(R.id.tv_receive)

        sendBtn.setOnClickListener {
            val sendData = inputEt.text.toString().toByteArray()
            runCatching { serialPort?.write(sendData, 1000) }
        }

        // 启动时枚举已连接的USB设备
        val availableDrivers = UsbSerialProber.getDefaultProber().findAllDrivers(usbManager)
        if (availableDrivers.isNotEmpty()) requestPermission(availableDrivers[0].device)
    }

    private fun requestPermission(device: UsbDevice) {
        val permissionIntent = PendingIntent.getBroadcast(
            this, 0, Intent(ACTION_USB_PERMISSION), PendingIntent.FLAG_MUTABLE
        )
        usbManager.requestPermission(device, permissionIntent)
    }

    private fun connectDevice(device: UsbDevice) {
        val driver = UsbSerialProber.getDefaultProber().probeDevice(device) ?: return
        val connection = usbManager.openDevice(driver.device) ?: return
        serialPort = driver.ports[0].apply {
            open(connection)
            // 波特率、停止位、校验位请根据你连接的实际设备修改,常见波特率为9600、115200
            setParameters(115200, 8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE)
        }

        // 启动串口数据监听
        ioManager = SerialInputOutputManager(serialPort, object : SerialInputOutputManager.Listener {
            override fun onNewData(data: ByteArray?) {
                data?.let {
                    val receivedStr = String(it)
                    runOnUiThread {
                        findViewById<TextView>(R.id.tv_receive).append("收到数据:$receivedStr\n")
                    }
                }
            }
            override fun onRunError(e: Exception?) { e?.printStackTrace() }
        })
        Executors.newSingleThreadExecutor().submit(ioManager)
    }

    private fun closeSerial() {
        if (::ioManager.isInitialized) ioManager.stop()
        runCatching { serialPort?.close() }
        serialPort = null
    }

    override fun onDestroy() {
        super.onDestroy()
        unregisterReceiver(usbReceiver)
        closeSerial()
    }
}

布局文件参考

res/layout/activity_main.xml使用简单的线性布局即可满足基础收发需求:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp">

    <EditText
        android:id="@+id/et_input"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="输入要发送的内容" />

    <Button
        android:id="@+id/btn_send"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="发送"
        android:layout_marginVertical="8dp" />

    <TextView
        android:id="@+id/tv_receive"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="16sp" />

</LinearLayout>

常见问题说明

  • 首次插入设备时系统会弹出权限申请框,必须点击允许才能建立通信
  • 串口参数(波特率、数据位、停止位、校验位)必须和外接USB设备的参数完全一致,否则会出现乱码、收不到数据的问题
  • Android 12及以上版本使用PendingIntent必须显式指定可变性标志,上述代码已适配FLAG_MUTABLE,不会出现编译报错
  • 若无法识别设备,先确认手机OTG功能已开启、OTG线和USB设备功能正常
  • 串口读写不能在主线程执行,上述代码使用库自带的IO线程处理接收逻辑,不会触发主线程阻塞异常

内容的提问来源于stack exchange,提问作者garrettb

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 10:03:22