简介

公司最近正好有个关于Android串口通信的项目,所以我花了一段时间学习并总结了一下,以便大家学习以及自己日后回顾。

话不多说,直接进入正题。我们都知道,Android串口通信要使用到JNI以及NDK的内容,但这一块的内容网上资源一抓一大把,这里就不加以赘述了。不了解的可以先去百度了解一下再来。

项目的配置

首先,关于JNI方面以及SO库的编译这方面的内容不包括在本文中,本文直接使用了GitHub上的一个叫android-serialport-api的开源项目,可以点此下载。或者稍候从我的源码里面拷贝。

下载完成后将jni以及jniLibs文件夹直接拉到java同级的目录下,如下:

Android Studio下的串口程序开发实战_第1张图片

然后在你的项目的java文件下创建一个包,包名为android_serialport_api。这里的包名对应的是jni中SerialPort.c文件中的方法名。对于JNI比较了解的应该都知道,JNI中的方法就是Java_包名_方法名,可以打开本项目中jni文件夹中的SerialPorat.c文件一看便知

Android Studio下的串口程序开发实战_第2张图片

如上所示,我们可以看到我们的方法名为:Java_android_serialport_api_SerialPort_close。所以如果你不想将你的包名命名为"android_serialport_api"的话,则需要将SerialPort.c文件中对应的方法名也改掉。

 

创建好包后,接下来就是在该包下新建一个SerialPort类,在这个类中我们主要创建两个本地方法,分别为open,以及close,运行时,Android会自动的调用jni中对应的方法,从而实现对指定串口的打开及关闭。

完整代码如下:

package android_serialport_api;import android.util.Log;import java.io.File;import java.io.FileDescriptor;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;public class SerialPort {    private static final String TAG = "SerialPort";    private FileDescriptor mFd;    private FileInputStream mFileInputStream;    private FileOutputStream mFileOutputStream;    public SerialPort(File device, int baudrate, int flags) throws SecurityException, IOException {        //检查访问权限,如果没有读写权限,进行文件操作,修改文件访问权限        if (!device.canRead() || !device.canWrite()) {            try {                //通过挂载到linux的方式,修改文件的操作权限                Process su = Runtime.getRuntime().exec("/system/xbin/su");                String cmd = "chmod 777 " + device.getAbsolutePath() + "\n" + "exit\n";                su.getOutputStream().write(cmd.getBytes());                if ((su.waitFor() != 0) || !device.canRead() || !device.canWrite()) {                    throw new SecurityException();                }            } catch (Exception e) {                e.printStackTrace();                throw new SecurityException();            }        }        mFd = open(device.getAbsolutePath(), baudrate, flags);        if (mFd == null) {            Log.e(TAG, "native open returns null");            throw new IOException();        }        mFileInputStream = new FileInputStream(mFd);        mFileOutputStream = new FileOutputStream(mFd);    }    // Getters and setters    public InputStream getInputStream() {        return mFileInputStream;    }    public OutputStream getOutputStream() {        return mFileOutputStream;    }    // JNI(调用java本地接口,实现串口的打开和关闭)/**串口有五个重要的参数:串口设备名,波特率,检验位,数据位,停止位 其中检验位一般默认位NONE,数据位一般默认为8,停止位默认为1*/    /**     * @param path     串口设备的据对路径     * @param baudrate 波特率     * @param flags    校验位     */    private native static FileDescriptor open(String path, int baudrate, int flags);    public native void close();    static {//加载jni下的C文件库        System.loadLibrary("serial_port");    }}

可以看到,这个类中除了上面提到的两个方法外,还在类的构造方法中对文件的操作权限进行了处理。

 

接下来,在你的项目的build.gradle(app)文件中加入这么几句话:

sourceSets {    main { jni.srcDirs = [] }}

将其放在buildTypes内部,如下:

buildTypes {    release {        minifyEnabled false        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'    }    sourceSets {        main { jni.srcDirs = [] }    }}

 

至此,项目的引入以及配置就完成了,接下来就只要将你的项目build一下,然后运行,能成功运行就表示配置完成了。

项目的编写

按照以上步骤将你的项目配置完成后,接下来就是开始编写你的项目了,我这里创建了一个工具类来对串口的打开、关闭、发送及接收进行处理。代码如下:

package com.warwee.serialporttest.utils;import android.os.Handler;import android.os.Message;import android.util.Log;import com.warwee.serialporttest.activity.MainActivity;import com.warwee.serialporttest.constants.IConstant;import java.io.File;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import android_serialport_api.SerialPort;/** * Created by Warwee on 2017/6/6. */public class SerialPortUtil {    public static SerialPort serialPort = null;    public static InputStream inputStream = null;    public static OutputStream outputStream = null;    public static Thread receiveThread = null;    public static boolean flag = false;    public static String serialData;    /**     * 打开串口的方法     */    public static void openSrialPort(){        Log.i("test","打开串口");        try {            serialPort = new SerialPort(new File("/dev/"+ IConstant.PORT),IConstant.BAUDRATE,0);            //获取打开的串口中的输入输出流,以便于串口数据的收发            inputStream = serialPort.getInputStream();            outputStream = serialPort.getOutputStream();            flag = true;            receiveSerialPort();        } catch (IOException e) {            e.printStackTrace();        }    }    /**     *关闭串口的方法     * 关闭串口中的输入输出流     * 然后将flag的值设为flag,终止接收数据线程     */    public static void closeSerialPort(){        Log.i("test","关闭串口");        try {            if(inputStream != null) {                inputStream.close();            }            if(outputStream != null){                outputStream.close();            }            flag = false;        } catch (IOException e) {            e.printStackTrace();        }    }    /**     * 发送串口数据的方法     * @param data 要发送的数据     */    public static void sendSerialPort(String data){        Log.i("test","发送串口数据");        try {            byte[] sendData = data.getBytes();            outputStream.write(sendData);            outputStream.flush();            Log.i("test","串口数据发送成功");        } catch (IOException e) {            e.printStackTrace();            Log.i("test","串口数据发送失败");        }    }    /**     * 接收串口数据的方法     */    public static void receiveSerialPort(){        Log.i("test","接收串口数据");        if(receiveThread != null)            return;        /*定义一个handler对象要来接收子线程中接收到的数据            并调用Activity中的刷新界面的方法更新UI界面         */        final Handler handler = new Handler(){            @Override            public void handleMessage(Message msg) {                if(msg.what==1){                    MainActivity.refreshTextView(serialData);                }            }        };        /*创建子线程接收串口数据         */        receiveThread = new Thread(){            @Override            public void run() {                while (flag) {                    try {                        byte[] readData = new byte[1024];                        if (inputStream == null) {                            return;                        }                        int size = inputStream.read(readData);                        if (size>0 && flag) {                            serialData = new String(readData,0,size);                            Log.i("test", "接收到串口数据:" + serialData);                            /*将接收到的数据封装进Message中,然后发送给主线程                             */                            handler.sendEmptyMessage(1);                            Thread.sleep(1000);                        }                    } catch (IOException e) {                        e.printStackTrace();                    }catch (InterruptedException e) {                        e.printStackTrace();                    }                }            }        };        //启动接收线程        receiveThread.start();    }}

 

以上需要注意一点的是,我这里接收到串口数据后,先通过handler消息传递机制传递给主线程,然后在主线程中调用Activity中的refreshTextView(String)方法将接收到字符串传到Activity使其更新界面显示。

接下来,就是创建一个Activity来调用该工具类中的方法以实现对于串口的处理。我这里就不贴出了,有需要的可以点击下载我的源码。

调试项目一些可能遇到的问题

要调试串口项目,首先,你必须要先连接串口。或者下载一个虚拟串口工具,创建两个虚拟串口并连接。

之后,可以去下载一个串口调试工具,以更直观的查看串口是否接收到数据。

如果前面使用的是虚拟串口的话,我们需要将两个已成功对接的串口的一端挂载到你的模拟器上。要挂载串口到模拟器上,需打开cmd,并输入以下指令:

emulator @你的模拟器名称 -qemu -serial 你要挂载到模拟器上的端口        如:emulator @4442 -qemu -serial COM1

使用指令前,请保证的要挂载的模拟器是关闭的,否则会挂载无效。输入指令后,你就可以看到你挂载的模拟器被启动了。

之后就可以在启动的模拟器中尝试发送或接受串口指令了。

 

一些可能遇到的问题请参考以下:

1.如果cmd提示emulator不是内部或外部指令或其他类似内容,请配置你的环境变量,具体请根据你的提示自行百度。

 

2.如果程序报SerialPort的26行,即:

Process su = Runtime.getRuntime().exec("/system/xbin/su");

IO异常,则可能你的模拟器没有root权限,具体开启方法,也请自行百度你使用的模拟器的root方法。

 

3.串口可以正常打开,但无法收发串口信息。

首先,确认你的两个串口是否正常连接。若已正常连接,且可以使用串口调试工具对两个串口互发数据,请往下看:

尝试更改你的串口号及波特率。如我的串口号是定义在一个接口中的常量,使用的是ttyS1。

 

4.接收到数据后,模拟器卡机。

关于此问题本人暂时也没有可行的解决方案,猜测是模拟器的问题,如有大神知晓,还请不吝赐教。

 

5.暂时只想到这些问题,如有遇到其它的问题,再另行补充。

小结

关于Android串口通信的内容就以上这么多,在此,特别鸣谢@atuan_wang的博客提供的灵感。

程序源码已上传CSDN,感兴趣的可以点击前去下载。

源码中,在SerialPortUtil类中的接收串口数据线程的实现有跟我这里列出的有一点不同,但功能上影响不大。

 

最后,本人第一次写博客,若有不好的地方,还请各位指教,不胜感激。

 

更多相关文章

  1. Android关于apk版本更新方法
  2. Android之gallery 常见2种使用方法和3D效果总结
  3. Android聊天客户端Demo,开源了.基本功能都有,数据库,服务器都Ok
  4. 调试方法-Unity3D对各个target平台的模拟
  5. Android读取工程内嵌资源文件的两种方法
  6. [翻译]Android教程-保存数据-支持不同的平台版本

随机推荐

  1. 详解在PHP模板引擎smarty生成随机数的方
  2. 基于PHP-FPM进程池的探索
  3. 示例php实现保存周期为1天的购物车类
  4. 详解PHP序列化和反序列化
  5. 分享几种PHP并发场景的解决方案
  6. 完整示例php+pdo实现的购物车类
  7. 分享php生成不重复随机数、数组的4种方法
  8. 详解php-fpm中max_children的配置
  9. 示例PHP实现单文件、多个单文件、多文件
  10. 聊聊PHP中的单例模式与静态变量