公司最近正好有个关于Android串口通信的模块,所以我学习并总结了一下,Android串口通信要使用到JNI以及NDK的内容
串口开发需要Root权限
关于串口的操作不外乎几步:
1.打开串口(及配置串口);
2.读串口;
3.写串口;
4.关闭串口。
第一:JNI技术,它使得java中可以调用c语言写成的库。源码:点击下载源码。
下载完成后将jni以及jniLibs文件夹直接拉到java同级的目录下,如下:
然后在你的项目的java文件下创建一个包,包名为android_serialport_api。这里的包名对应的是jni中SerialPort.c文件中的方法名。
如果你不想将你的包名命名为"android_serialport_api"的话,则需要将SerialPort.c文件中对应的方法名也改掉。
创建好包后,接下来就是在该包下新建一个SerialPort类,在这个类中我们主要创建两个本地方法,分别为open,以及close,
运行时,Android会自动的调用jni中对应的方法,从而实现对指定串口的打开及关闭。
- package android.serialport;
- 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;
- import android.util.Log;
- public class SerialPort {
- private static final String TAG = "SerialPort";
- /*
- * Do not remove or rename the field mFd: it is used by native method close();
- */
- private FileDescriptor mFd;
- private FileInputStream mFileInputStream;
- private FileOutputStream mFileOutputStream;
- public SerialPort(File device, int baudrate) throws SecurityException, IOException {
- /* Check access permission */
- if (!device.canRead() || !device.canWrite()) {
- try {
- /* Missing read/write permission, trying to chmod the file */
- Process su;
- su = Runtime.getRuntime().exec("/system/bin/su");
- String cmd = "chmod 777 " + device.getAbsolutePath() + "\n"
- + "exit\n";
- /*String cmd = "chmod 777 /dev/s3c_serial0" + "\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);
- 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
- private native static FileDescriptor open(String path, int baudrate);
- public native void close();
- static {
- System.loadLibrary("serial_port");
- }
- }
可以看到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一下,然后运行,能成功运行就表示配置完成了。
按照以上步骤将你的项目配置完成后,接下来就是开始编写你的项目了,我这里创建了一个工具类来对串口的打开、关闭、发送及接收进行处理。
代码如下:
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来调用该工具类中的方法以实现对于串口的处理。我这里就不贴出了,
一些可能遇到的问题请参考以下:
1、如果程序报SerialPort的26行,即:
Process su = Runtime.getRuntime().exec("/system/xbin/su");
IO异常,则可能你的模拟器没有root权限,具体开启方法,也请自行百度你使用的模拟器的root方法。
2、串口可以正常打开,但无法收发串口信息。
首先,确认你的两个串口是否正常连接。若已正常连接,且可以使用串口调试工具对两个串口互发数据,请往下看:
尝试更改你的串口号及波特率。如我的串口号是定义在一个接口中的常量,使用的是ttyS1。
若有不好的地方,还请各位指教,不胜感激