最早的博客Android 模拟串口通信过程_launch virtual serial port driver pro-CSDN博客里就是用过 Google 提供的 demo,最近想再写个其他的demo发现用起来有点麻烦,还需要导入其他 module,因此在网上找到了Android-SerialPort-API: https://github.com/licheedev/Android-SerialPort-API.git 也是Fork自Google开源的Android串口通信Demo。

(图片来源网络,侵删)
话不多说,直接开搞。

(图片来源网络,侵删)
一、简单说明
1、添加依赖
2、创建串口通讯工具类 SerialPortUtil
3、示例 MainActivity
4、通过logcat验证app
二、注意
三、总结
四、demo地址
一、简单说明
1、添加依赖
dependencies { ... //添加依赖 implementation ("com.licheedev:android-serialport:2.1.3") }
2、创建串口通讯工具类 SerialPortUtil
class SerialPortUtil { companion object { private const val TAG = "SerialPortUtil" val sInstances by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { SerialPortUtil() } } private lateinit var mIOpenSerialPortListener: IOpenSerialPortListener private lateinit var mISerialPortDataListener: ISerialPortDataListener private lateinit var mSendingHandlerThread: HandlerThread private lateinit var mSendingHandler: Handler private lateinit var mSerialPortReceivedThread: SerialPortReceivedThread private lateinit var mFileInputStream: FileInputStream private lateinit var mFileOutputStream: FileOutputStream private lateinit var serialPort: SerialPort /** * 打开串口方法 */ fun open(path: File) { try { serialPort = SerialPort .newBuilder(path, 9600) // 校验位;0:无校验位(NONE,默认);1:奇校验位(ODD);2:偶校验位(EVEN) .build() mFileInputStream = serialPort.inputStream as FileInputStream mFileOutputStream = serialPort.outputStream as FileOutputStream } catch (e: SecurityException) { mIOpenSerialPortListener.onFail(path, Status.NO_READ_WRITE_PERMISSION) return } catch (e: Exception) { mIOpenSerialPortListener.onFail(path, Status.OPEN_FAIL) return } mIOpenSerialPortListener.onSuccess(path) startSendThread() startReceivedThread() } /** * 发送数据 */ fun sendBytes(bytes: ByteArray?): Boolean { try { Runnable { val message = Message.obtain() message.obj = bytes mSendingHandler.sendMessage(message) Thread.sleep(100) }.run() } catch (e: Exception) { Log.e(TAG, "sendBytes: 发送数据失败 " + e.message) return false } return true } /** * 开启发送消息线程 */ private fun startSendThread() { Log.d(TAG, "startSendThread: 开启发送消息线程") mSendingHandlerThread = HandlerThread("mSendingHandlerThread") mSendingHandlerThread.start() mSendingHandler = object : Handler(mSendingHandlerThread.looper) { override fun handleMessage(msg: Message) { val sendBytes: ByteArray? = msg.obj as ByteArray? if ((null != sendBytes) && (sendBytes.isNotEmpty())) { try { mFileOutputStream.write(sendBytes) mISerialPortDataListener.onDataSend(sendBytes) } catch (e: java.io.IOException) { e.printStackTrace() } } } } } /** * 停止发送消息线程 */ fun stopSendThread() { Log.d(TAG, "stopSendThread: 停止发送消息线程") mSendingHandlerThread.interrupt() mSendingHandlerThread.quit() } /** * 开启接收消息的线程 */ private fun startReceivedThread() { Log.d(TAG, "startReceivedThread: 开启接受消息线程") mSerialPortReceivedThread = object : SerialPortReceivedThread(mFileInputStream) { override fun onDataReceived(bytes: ByteArray?) { mISerialPortDataListener.onDataReceived(bytes) } } mSerialPortReceivedThread.start() } /** * 停止接收消息的线程 */ fun stopReceivedThread() { Log.d(TAG, "stopReceivedThread: 停止接收消息的线程") mSerialPortReceivedThread.release() serialPort.tryClose() } /** * 设置串口打开的监听 */ fun setIOpenSerialPortListener(iOpenSerialPortListener: IOpenSerialPortListener) { mIOpenSerialPortListener = iOpenSerialPortListener } /** * 设置串口数据收发的监听 */ fun setISerialPortDataListener(iSerialPortDataListener: ISerialPortDataListener) { mISerialPortDataListener = iSerialPortDataListener } }
在这里简单介绍些,在开启串口通讯后开启两个线程,分别处理send及received步骤。
3、示例 MainActivity
class MainActivity : Activity(), IOpenSerialPortListener, ISerialPortDataListener { companion object { private const val TAG = "MainActivity" private val FIND_CARD = byteArrayOf(0x20, 0x00, 0x80.toByte(), 0x04, 0x03, 0x03, 0x01, 0x00, 0x7a, 0x03) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) CrashHandler.sInstance.init(this) //0、设置su位置 SerialPort.setSuPath("/system/xbin/su") //1、首先设置open监听 SerialPortUtil.sInstances.setIOpenSerialPortListener(this) //2、设置串口监听 SerialPortUtil.sInstances.setISerialPortDataListener(this) //3、open串口 SerialPortUtil.sInstances.open(File("/dev/ttyS3")) //4、模拟发送命令 SerialPortUtil.sInstances.sendBytes(FIND_CARD) } override fun onDestroy() { super.onDestroy() SerialPortUtil.sInstances.stopSendThread() SerialPortUtil.sInstances.stopReceivedThread() } override fun onSuccess(device: File?) { Log.d(TAG, "onSuccess: open成功") } override fun onFail(device: File?, status: Status?) { Log.d(TAG, "onFail: open失败,原因: $status") } override fun onDataReceived(bytes: ByteArray?) { Log.d(TAG, "onDataReceived: 接受数据: " + Arrays.toString(bytes)) } override fun onDataSend(bytes: ByteArray?) { Log.d(TAG, "onDataSend: 发送数据: " + Arrays.toString(bytes)) } }
4、通过logcat验证app
09:52:48.349 serial_port D Opening serial port /dev/ttyS3 with flags 0x2 09:52:48.349 serial_port D open() fd = 45 09:52:48.349 serial_port D Configuring serial port 09:52:48.359 MainActivity D onSuccess: open成功 09:52:48.359 SerialPortUtil D startSendThread: 开启发送消息线程 09:52:48.359 SerialPortUtil D startReceivedThread: 开启接受消息线程 09:52:48.359 SerialPortReceivedThread I run: available = 0 09:52:48.359 MainActivity D onDataSend: 发送数据: [32, 0, -128, 4, 3, 3, 1, 0, 122, 3] 09:52:48.529 BufferQueue E [com.lichang.source/com.lichang.source.MainActivity] connect: already connected (cur=1, req=1) 09:52:48.529 mali_winsys D EGLint new_window_surface(egl_winsys_display*, void*, EGLSurface, EGLConfig, egl_winsys_surface**, egl_color_buffer_format*, EGLBoolean) returns 0x3000 09:52:48.529 OpenGLRenderer D Enabling debug mode 0 09:52:51.609 SerialPortReceivedThread I run: size = 14 09:52:51.609 SerialPortReceivedThread I run: bytes = [32, 0, 0, 8, 4, 0, 8, 4, 28, 37, -117, -5, -74, 3] 09:52:51.609 MainActivity D onDataReceived: 接受数据: [32, 0, 0, 8, 4, 0, 8, 4, 28, 37, -117, -5, -74, 3] 09:52:51.609 SerialPortReceivedThread I run: available = 0 09:52:53.729 SerialPortReceivedThread I run: size = 22 09:52:53.729 SerialPortReceivedThread I run: bytes = [32, 0, 0, 16, 32, 17, 35, 69, 103, -113, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 80, 3] 09:52:53.729 MainActivity D onDataReceived: 接受数据: [32, 0, 0, 16, 32, 17, 35, 69, 103, -113, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 80, 3] 09:52:53.729 SerialPortReceivedThread I run: available = 0 09:53:03.239 dalvikvm D Debugger has detached; object registry had 1 entries 09:53:03.249 dalvikvm D GC_CONCURRENT freed 222K, 15% free 2334K/2744K, paused 1ms+1ms, total 12ms 09:53:12.419 SerialPortUtil D stopSendThread: 停止发送消息线程 09:53:12.419 SerialPortUtil D stopReceivedThread: 停止接收消息的线程 09:53:12.419 serial_port D close(fd = 45)
二、注意
为了在Android上读/写串行端口,你需要在设备上安装su binary(这可以通过root设备来完成)。通常,具有串口通信能力的Android设备已将su安装在默认路径下。
默认的 su 路径使用的是 “/system/bin/su”
三、总结
在新项目中快速应用,可以先导入依赖,然后copy示例中的 com/lichang/source/serialport文件夹,即可按照 3、示例 MainActivity 钓箱串口工具类。
四、demo地址
serialport-demo_kt: 使用 implementation ("com.licheedev:android-serialport:2.1.3")