Android 串口通信笔记2 调试工具分析 工具类实现分析、项目实现
2018-03-08 16:19
711 查看
Android 串口通信笔记2 调试工具分析 工具类实现分析、项目实现
1.调试工具ComAssistant 分析
ComAssistant
Android 端调试工具ComAssistant 如图,处于何人之手已不可考,找到的源码是用eclipse 写的。源码见文末分享。
此串口调试工具,可以同时对四个串口读写是四个独立的线程,选定串口路径 ,Linux把每个硬件也看作是一个文件,所以都是“dev/ttyS1”这种的。注意:官方提供的 demo 没有N-8-1( N 不奇偶校验位 8 8个数据位 1 1个停止位)的设定。
第一次根据设备终端说明或者自己尝试连接电脑打开调试助手 查看到底哪个口对应哪个路径。
2.源码分析:
Eclipse版本的从哪个资源网站下载的忘记了,不过解压看是2012年8月的,所以这里边的api 适配到10(API等级10:Android 2.3.3-2.3.7 Gingerbread 姜饼),Eclipse项目结构:Eclipse项目结构从结构中可以看出来 是把Android官方提供的android_serial_api 从项目包中独立出来,此源码唯一不好的是 GBK 编码的 导入Android Studio中时 乱码 要从新折腾。SerialPortFinder与SerialPort分析:
SerialPortFinderSerialPortFinder就是遍历获取设备上所有devices以及对应的path;
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, int flags) 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 666 " + 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 private native static FileDescriptor open(String path, int baudrate, int flags); public native void close(); static { System.loadLibrary("serial_port"); } }创建了打开串口和关闭串口的本地方法,在jni中实现,给Java层调用。
主要是分析 SerialHelp和 Activity的实现逻辑,SerialHelper代码:
public abstract class SerialHelper{ private SerialPort mSerialPort; private OutputStream mOutputStream; private InputStream mInputStream; private ReadThread mReadThread; private SendThread mSendThread; private String sPort="/dev/s3c2410_serial0"; private int iBaudRate=9600; private boolean _isOpen=false; private byte[] _bLoopData=new byte[]{0x30}; private int iDelay=500; //---------------------------------------------------- public SerialHelper(String sPort,int iBaudRate){ this.sPort = sPort; this.iBaudRate=iBaudRate; } public SerialHelper(){ this("/dev/s3c2410_serial0",9600); } public SerialHelper(String sPort){ this(sPort,9600); } public SerialHelper(String sPort,String sBaudRate){ this(sPort,Integer.parseInt(sBaudRate)); } //---------------------------------------------------- public void open() throws SecurityException, IOException,InvalidParameterException{ File device = new File(sPort); //检查访问权限,如果没有读写权限,进行文件操作,修改文件访问权限 if (!device.canRead() || !device.canWrite()) { try { //通过挂在到linux的方式,修改文件的操作权限 Process su = Runtime.getRuntime().exec("/system/bin/su"); //一般的都是/system/bin/su路径,有的也是/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(); } } mSerialPort = new SerialPort(new File(sPort), iBaudRate, 0); mOutputStream = mSerialPort.getOutputStream(); mInputStream = mSerialPort.getInputStream(); mReadThread = new ReadThread(); mReadThread.start(); mSendThread = new SendThread(); mSendThread.setSuspendFlag(); mSendThread.start(); _isOpen=true; } //---------------------------------------------------- public void close(){ if (mReadThread != null) mReadThread.interrupt(); if (mSerialPort != null) { mSerialPort.close(); mSerialPort = null; } _isOpen=false; } //---------------------------------------------------- public void send(byte[] bOutArray){ try { mOutputStream.write(bOutArray); } catch (IOException e) { e.printStackTrace(); } } //---------------------------------------------------- public void sendHex(String sHex){ byte[] bOutArray = MyFunc.HexToByteArr(sHex); send(bOutArray); } //---------------------------------------------------- public void sendTxt(String sTxt){ byte[] bOutArray =sTxt.getBytes(); send(bOutArray); } //---------------------------------------------------- private class ReadThread extends Thread { @Override public void run() { super.run(); while(!isInterrupted()) { try { if (mInputStream == null) return; byte[] buffer=new byte[512]; int size = mInputStream.read(buffer); if (size > 0){ ComBean ComRecData = new ComBean(sPort,buffer,size); onDataReceived(ComRecData); } try { Thread.sleep(50);//延时50ms } catch (InterruptedException e) { e.printStackTrace(); } } catch (Throwable e) { e.printStackTrace(); return; } } } } //---------------------------------------------------- private class SendThread extends Thread{ public boolean suspendFlag = true;// 控制线程的执行 @Override public void run() { super.run(); while(!isInterrupted()) { synchronized (this) { while (suspendFlag) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } send(getbLoopData()); try { Thread.sleep(iDelay); } catch (InterruptedException e) { e.printStackTrace(); } } } //线程暂停 public void setSuspendFlag() { this.suspendFlag = true; } //唤醒线程 public synchronized void setResume() { this.suspendFlag = false; notify(); } } //---------------------------------------------------- public int getBaudRate() { return iBaudRate; } public boolean setBaudRate(int iBaud) { if (_isOpen) { return false; } else { iBaudRate = iBaud; return true; } } public boolean setBaudRate(String sBaud) { int iBaud = Integer.parseInt(sBaud); return setBaudRate(iBaud); } //---------------------------------------------------- public String getPort() { return sPort; } public boolean setPort(String sPort) { if (_isOpen) { return false; } else { this.sPort = sPort; return true; } } //---------------------------------------------------- public boolean isOpen() { return _isOpen; } //---------------------------------------------------- public byte[] getbLoopData() { return _bLoopData; } //---------------------------------------------------- public void setbLoopData(byte[] bLoopData) { this._bLoopData = bLoopData; } //---------------------------------------------------- public void setTxtLoopData(String sTxt){ this._bLoopData = sTxt.getBytes(); } //---------------------------------------------------- public void setHexLoopData(String sHex){ this._bLoopData = MyFunc.HexToByteArr(sHex); } //---------------------------------------------------- public int getiDelay() { return iDelay; } //---------------------------------------------------- public void setiDelay(int iDelay) { this.iDelay = iDelay; } //---------------------------------------------------- public void startSend() { if (mSendThread != null) { mSendThread.setResume(); } } //---------------------------------------------------- public void stopSend() { if (mSendThread != null) { mSendThread.setSuspendFlag(); } } //---------------------------------------------------- protected abstract void onDataReceived(ComBean ComRecData); }除去一些get set方法 ,主要是 构造方法 ,打开关闭方法 以及最后一行的abstract 方法onDataReceived()和一个读的线程ReadThread 和一个发送命令线程SendThread ;在ReadThread 在接收或者叫读线程中 调用了onDataReceived()方法这样在用的时候 可以直接实现调用。SendThread 中 自动发 的原理就是 执行while语句发送命令 线程sleep()来间隔循环,控制线程暂停和唤起用的是 wait()和notif(),所以就可以通过设定flag实现自动发送。
wait() 与 notify/notifyAll 方法必须在同步代码块(synchronized关键字)中使用.由于 wait() 与 notify/notifyAll() 是放在同步代码块中的,因此线程在执行它们时,肯定是进入了临界区中的,即该线程肯定是获得了锁的。
当线程执行wait()时,会把当前的锁释放,然后让出CPU,进入等待状态。
当执行notify/notifyAll方法时,会唤醒一个处于等待该 对象锁 的线程,然后继续往下执行,直到执行完退出对象锁锁住的区域(synchronized修饰的代码块)后再释放锁。ReadThread 就简单了也是while()代码块 定时sleep循环 之后 读到内容之后封装成实体对象调用抽象方法onDataReceived()传递到要实现的地方。MyFunc是一些数据转换的静态方法,如图:
MyFuncComAssistantActivity的大致截图 770行
ComAssistantActivityComAssistantActivity中 数据比较多,但是也不难捋顺,从左侧概要中可以看出来主要是一些事件处理和两个继承类:串口控制类SerialControl 继承SerialHelper和刷新显示线程DispQueueThread
如图是Activity onCreate()是实例化四个串口控制SerialControl 对象以及刷新线程并启动。
image.png
//----------------------------------------------------串口控制类 private class SerialControl extends SerialHelper{ public SerialControl(){ } @Override protected void onDataReceived(final ComBean ComRecData) { //数据接收量大或接收时弹出软键盘,界面会卡顿,可能和6410的显示性能有关 //直接刷新显示,接收数据量大时,卡顿明显,但接收与显示同步。 //用线程定时刷新显示可以获得较流畅的显示效果,但是接收数据速度快于显示速度时,显示会滞后。 //最终效果差不多-_-,线程定时刷新稍好一些。 DispQueue.AddQueue(ComRecData);//线程定时刷新显示(推荐) Log.e("TAG", MyFunc.ByteArrToHex(ComRecData.bRec)); /* runOnUiThread(new Runnable()//直接刷新显示 { public void run() { DispRecData(ComRecData); } });*/ } }SerialControl 继承SerialHelper,那么它的实例就可以对串口进行读写操作 并且 在onDataReceived()中实现对接收到的数据进行处理。即添加到 刷新线程的 数据源队列中:DispQueue是DispQueueThread 的实例。
//----------------------------------------------------刷新显示线程 private class DispQueueThread extends Thread{ private Queue<ComBean> QueueList = new LinkedList<ComBean>(); @Override public void run() { super.run(); while(!isInterrupted()) { final ComBean ComData; while((ComData=QueueList.poll())!=null) { runOnUiThread(new Runnable() { public void run() { DispRecData(ComData);//更新界面 } }); try { Thread.sleep(100);//显示性能高的话,可以把此数值调小。 } catch (Exception e) { e.printStackTrace(); } break; } } } public synchronized void AddQueue(ComBean ComData){ QueueList.add(ComData); } }其中QueueList做为接收到的数据存放队列,LinkedList是有序的,为什么AddQueue要同步加锁呢
public synchronized void AddQueue(ComBean ComData){ QueueList.add(ComData); }因为LinkedList是线程不安全的,开启了四个串口控制对象如果同时add()会抛出ConcurrentModificationException异常。while语句执行的条件LinkedList.poll()方法的含义:找到并删除表头,返回null或队列中第一个对象,还是用源码来分析LinkedList
public E poll() { return size == 0 ? null : removeFirst(); } /** * Removes the first object from this {@code LinkedList}. * * @return the removed object. * @throws NoSuchElementException * if this {@code LinkedList} is empty. */ public E removeFirst() { return removeFirstImpl(); } private E removeFirstImpl() { Link<E> first = voidLink.next; if (first != voidLink) { Link<E> next = first.next; voidLink.next = next; next.previous = voidLink; size--; modCount++; return first.data; } throw new NoSuchElementException(); }
3.项目实现
用该eclipse项目源码 做尝试移植了一份Android Studio 3.0 的项目,几番测试通过打的包也能用,同比可以迁移到自己项目。代码分享文末;Android Studio移植实现在main 目录下创建 jni 和jniLibs ,
0.把原Eclipse项目的android_serialport_api包复制到在main/java下。
1.把原eclipse中的libs路径下的三个平台的serial_port.so同目录复制到jniLibs下。
2.把原eclipse中的c .h 文件复制到jni并重命名为android_serialport_api_SerialPort,或者使用Terminal命令生成C的头文件自己在把代码复制进去(注意路径对应方法名,这个1应该是区分包名和下划线:Java_android_1serialport_1api_SerialPort_open)
Terminal命令
①输入cd app\src\main\java进入源码所在目录 ②输入javah -jni android_serialport_api.SerialPort生成头文件 ③把生成的android_serialport_api_SerialPort.h复制到jni下边(没有该目录就右键 Moudle,右键菜单中选择 New -> Folder -> JNI Folder) ④右键 jni 文件夹,右键菜单中选择New -> C/C++ Source File创建与 .h 文件同名的 .c 文件。 ⑤把原Eclipse 的jni下对应的.c .h文件代码复制进去3.在build.gradle 的android节点中添加
sourceSets.main { jniLibs.srcDir 'src/main/jniLibs' jni.srcDirs = [] }上图的右侧标红部分,否则会提示
Flag android.useDeprecatedNdk is no longer supported and will be removed in the next version of Android Studio. Please switch to a supported build system.这样就直接可以用原项目编译好的.so 注意前提是要在在 local.properties 添加 ndk 路径:
#Sat Jan 20 10:09:24 CST 2018 ndk.dir=F\:\\sdk\\ndk-bundle sdk.dir=F\:\\sdk其下目录有Eclipse 项目源码和Android Studio 源码 以及自己使用本机debug 密钥打包的 Android调试工具和 PC 端调试工具,github 地址 https://github.com/silencefun/ComTest百度云链接: https://pan.baidu.com/s/1nw37xu5 密码: qscc原文发自简书 https://www.jianshu.com/p/d3d65230ecdb如果觉得有帮助,请点个赞❤ ★,谢谢。
相关文章推荐
- 使用网络流量分析工具更好调试Android应用
- 【堆调试工具】pageheap的使用和原理分析&Linux下相似的功能实现
- Android内存分析工具(六):开源项目Emmagee
- 酷炫开源项目cardsui-for-android-超详细源码分析,详解所用特效是如何实现的
- android推送,androidpn项目分析及完善学习笔记(三) XMPP协议
- 项目分析笔记(2)---聊天室项目分析与实现(2008.6.12)
- 使用网络流量分析工具更好调试Android应用(转)
- Android 学习笔记之十一 2048的实现分析
- Android项目下目录分析(Android第一行代码学习笔记3)
- 网上图书商城项目学习笔记-025分类管理模块分析及查询所有分类实现
- 12.11 阅读android项目源码笔记-水波view,左右翻页三种实现,图片、文件加密
- Android笔记之:App调试的几个命令的实践与分析
- 项目分析笔记(1)---聊天室项目分析与实现(2008.6.11)
- android推送,androidpn项目分析及完善学习笔记(二) Mina
- Android应用性能分析调试工具 TraceView
- Android 个人理财工具一:项目概述与启动界面的实现
- Android笔记之:App调试的几个命令的实践与分析
- Android项目笔记一:TextView+ScrollView 实现垂直滚动条
- maven构建android项目后,通过eclipse集成工具右键命令行方式部署到模拟器上进行调试
- Android 项目实践(五)——基础类和工具类的实现