求可正常编译的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
相关产品推荐
相关产品推荐

