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")

相关推荐
雨白1 天前
Jetpack Compose Navigation 2.x 详解
android·android jetpack
Android系统攻城狮1 天前
Android内核进阶之获取DMA地址snd_pcm_sgbuf_get_addr:用法实例(九十一)
android·pcm·android内核·音频进阶·pcm硬件参数
清空mega1 天前
Android Studio移动应用基础教程(前言)
android·ide·android studio
2501_937145411 天前
2025IPTV 源码优化版实测:双架构兼容 + 可视化运维
android·源码·源代码管理·机顶盒
zhoutanooi1 天前
安卓bp文件编译学习
android·学习
aramae1 天前
MySQL数据库入门指南
android·数据库·经验分享·笔记·mysql
百锦再1 天前
选择Rust的理由:从内存管理到抛弃抽象
android·java·开发语言·后端·python·rust·go
whatever who cares1 天前
在Java/Android中,List的属性和方法
android·java
油炸小波1 天前
09-微服务原理篇(XXLJOB-幂等-MySQL)
android·mysql·微服务
果子没有六分钟1 天前
setprop debug.hwui.profile visual_bars有什么作用
android