Android - 串口通讯(SerialPort)

最早的博客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](#2、创建串口通讯工具类 SerialPortUtil)

[3、示例 MainActivity](#3、示例 MainActivity)

4、通过logcat验证app

二、注意

三、总结

四、demo地址


一、简单说明

1、添加依赖
复制代码
dependencies {

...
    //添加依赖
    implementation ("com.licheedev:android-serialport:2.1.3")

}
2、创建串口通讯工具类 SerialPortUtil
Kotlin 复制代码
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
Kotlin 复制代码
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
Kotlin 复制代码
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")

相关推荐
雨白11 小时前
Android 快捷方式实战指南:静态、动态与固定快捷方式详解
android
hqk11 小时前
鸿蒙项目实战:手把手带你实现 WanAndroid 布局与交互
android·前端·harmonyos
LING12 小时前
RN容器启动优化实践
android·react native
恋猫de小郭14 小时前
Flutter 发布官方 Skills ,Flutter 在 AI 领域再添一助力
android·前端·flutter
Kapaseker20 小时前
一杯美式搞懂 Any、Unit、Nothing
android·kotlin
黄林晴20 小时前
你的 Android App 还没接 AI?Gemini API 接入全攻略
android
恋猫de小郭1 天前
2026 Flutter VS React Native ,同时在 AI 时代 VS Native 开发,你没见过的版本
android·前端·flutter
冬奇Lab1 天前
PowerManagerService(上):电源状态与WakeLock管理
android·源码阅读
BoomHe1 天前
Now in Android 架构模式全面分析
android·android jetpack
二流小码农2 天前
鸿蒙开发:上传一张参考图片便可实现页面功能
android·ios·harmonyos