利用JNI写的安卓串口读写框架

来源:互联网 发布:手机做表格软件 编辑:程序博客网 时间:2024/06/14 23:13

C代码如下:

#include <termios.h>#include <unistd.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <string.h>#include <jni.h>//#include "SerialPort.h"#include "android/log.h"static const char *TAG="serial_port";#define LOGI(fmt, args...) __android_log_print(ANDROID_LOG_INFO,  TAG, fmt, ##args)#define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, TAG, fmt, ##args)#define LOGE(fmt, args...) __android_log_print(ANDROID_LOG_ERROR, TAG, fmt, ##args)static speed_t getBaudrate(jint baudrate){switch(baudrate){case 0: return B0;case 50: return B50;case 75: return B75;case 110: return B110;case 134: return B134;case 150: return B150;case 200: return B200;case 300: return B300;case 600: return B600;case 1200: return B1200;case 1800: return B1800;case 2400: return B2400;case 4800: return B4800;case 9600: return B9600;case 19200: return B19200;case 38400: return B38400;case 57600: return B57600;case 115200: return B115200;case 230400: return B230400;case 460800: return B460800;case 500000: return B500000;case 576000: return B576000;case 921600: return B921600;case 1000000: return B1000000;case 1152000: return B1152000;case 1500000: return B1500000;case 2000000: return B2000000;case 2500000: return B2500000;case 3000000: return B3000000;case 3500000: return B3500000;case 4000000: return B4000000;default: return -1;}}/* * Class:     android_serialport_SerialPort * Method:    open * Signature: (Ljava/lang/String;II)Ljava/io/FileDescriptor; */JNIEXPORT jobject JNICALL Java_com_cjz_jniserialtest_SerialUtil_open (JNIEnv *env, jclass thiz, jstring path, jint baudRate){int fd;speed_t speed;jobject mFileDescriptor;/* Check arguments */{speed = getBaudrate(baudRate);if (speed == -1) {/* TODO: throw an exception *///LOGE("Invalid baudrate");return NULL;}}/* Opening device */{jboolean iscopy;const char *path_utf = (*env)->GetStringUTFChars(env, path, &iscopy);//LOGD("Opening serial port %s with flags 0x%x", path_utf, O_RDWR | flags);fd = open(path_utf, O_RDWR/* | flags*/);//LOGD("open() fd = %d", fd);(*env)->ReleaseStringUTFChars(env, path, path_utf);if (fd == -1){/* Throw an exception *///LOGE("Cannot open port");/* TODO: throw an exception */return NULL;}}/* Configure device */{struct termios cfg;//LOGD("Configuring serial port");if (tcgetattr(fd, &cfg)){//LOGE("tcgetattr() failed");close(fd);/* TODO: throw an exception */return NULL;}cfmakeraw(&cfg);cfsetispeed(&cfg, speed);cfsetospeed(&cfg, speed);if (tcsetattr(fd, TCSANOW, &cfg)){//LOGE("tcsetattr() failed");close(fd);/* TODO: throw an exception */return NULL;}}/* Create a corresponding file descriptor */{jclass cFileDescriptor = (*env)->FindClass(env, "java/io/FileDescriptor");jmethodID iFileDescriptor = (*env)->GetMethodID(env, cFileDescriptor, "<init>", "()V");jfieldID descriptorID = (*env)->GetFieldID(env, cFileDescriptor, "descriptor", "I");mFileDescriptor = (*env)->NewObject(env, cFileDescriptor, iFileDescriptor);(*env)->SetIntField(env, mFileDescriptor, descriptorID, (jint)fd);}return mFileDescriptor;}/* * Class:     cedric_serial_SerialPort * Method:    close * Signature: ()V */JNIEXPORT void JNICALL Java_com_cjz_jniserialtest_SerialUtil_close(JNIEnv *env, jobject thiz){jclass SerialPortClass = (*env)->GetObjectClass(env, thiz);jclass FileDescriptorClass = (*env)->FindClass(env, "java/io/FileDescriptor");jfieldID mFdID = (*env)->GetFieldID(env, SerialPortClass, "mFd", "Ljava/io/FileDescriptor;");jfieldID descriptorID = (*env)->GetFieldID(env, FileDescriptorClass, "descriptor", "I");jobject mFd = (*env)->GetObjectField(env, thiz, mFdID);jint descriptor = (*env)->GetIntField(env, mFd, descriptorID);//LOGD("close(fd = %d)", descriptor);close(descriptor);}



JAVA部分:

记得开一个com.cjz.jniserialtest的包去放这些类,否则JNI无法联系到类和C代码接口,导致出错

SerialUtil:


package com.cjz.jniserialtest;import java.io.BufferedReader;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.InputStreamReader;import java.io.OutputStream;import java.util.LinkedList;import android.util.Log;import com.hanlion.cardofclass.utils.CommUtils;import com.hanlion.cardofclass.utils.LogUtils;/** * @author 陈杰柱 * @version 串口读写框架 1.0 * * 当观察者模式开启时,最好不要使用其他单独方法,否则容易因为 * 争夺数据的原因导致出错 ***/public class SerialUtil{private FileDescriptor mFd;private FileInputStream mFileInputStream;private FileOutputStream mFileOutputStream;private ThreadTimeCount threadTimeCount;private Thread threadReadData;private boolean observerMode = false;private static final String TAG = "SerialPortMsg";/**波特率列表类**/public class BaudRateList{public static final int B50 = 50;public static final int B75 = 75;public static final int B110 = 110;public static final int B134 = 134;public static final int B150 = 150;public static final int B200 = 200;public static final int B300 = 300;public static final int B600 = 600;public static final int B1200 = 1200;public static final int B1800 = 1800;public static final int B2400 = 2400;public static final int B4800 = 4800;public static final int B9600 = 9600;public static final int B19200 = 19200;public static final int B38400 = 38400;public static final int B57600 = 57600;public static final int B115200 = 115200;public static final int B230400 = 230400;public static final int B460800 = 460800;public static final int B500000 = 500000;public static final int B576000 = 576000;public static final int B921600 = 921600;public static final int B1000000 = 1000000;public static final int B1152000 = 1152000;public static final int B1500000 = 1500000;public static final int B2000000 = 2000000;public static final int B2500000 = 2500000;public static final int B3000000 = 3000000;public static final int B3500000 = 3500000;public static final int B4000000 = 4000000;}/**打开对应窗口内IO设备,支持USB转串口 * @param path 设备文件位置(inux把所有设备视为一个一维文件,不过按照图灵机概念本来计算机就是“纸带+读写头”) * @param baudRate 波特率设置,**/public native static synchronized FileDescriptor open(String path, int baudRate);/**关闭对应窗口内IO设备**/public native static synchronized FileDescriptor close();static{System.loadLibrary("JNISerialCtrl");}/**打开对应窗口内IO设备,支持USB转串口 * @param path 设备文件位置(Linux把所有设备视为一个一维文件,不过按照图灵机概念本来计算机就是“纸带+读写头”) * @param baudRate 波特率设置,**/public SerialUtil(String devicePath, int baudrate){/* Check access permission */File device = new File(devicePath);if(!device.exists()) return ;if (!device.canRead() || !device.canWrite()){try{/* Missing read/write permission, trying to chmod the file */Process su;su = Runtime.getRuntime().exec(CommUtils.getSuPath());String cmd = "chmod 666 " + device.getAbsolutePath() + "\n" + "exit\n";su.getOutputStream().write(cmd.getBytes());if ((su.waitFor() != 0) || !device.canRead() || !device.canWrite()){LogUtils.logE("串口打开失败");}} catch (Exception e){e.printStackTrace();}}mFd = open(device.getAbsolutePath(), baudrate);if (mFd == null){Log.e(TAG, "native open returns null");//throw new IOException();return ;}mFileInputStream = new FileInputStream(mFd);mFileOutputStream = new FileOutputStream(mFd);}/**获取设备输入流(主动捕获模式)**/public InputStream getInputStream(){return mFileInputStream;}/**获取设备输出流(主动捕获模式)**/public OutputStream getOutputStream(){return mFileOutputStream;}/**获取Reader,以文本方式读取(当回车时结束)(主动捕获模式,不关闭)**/public BufferedReader getBufferedReader(){try {return new BufferedReader(new InputStreamReader(getInputStream()));} catch (Exception e) {return null;}}/**以Byte链表方式读取(主动捕获模式) * @param spiltTimeLengthMS 隔多久没收到数据就视为本次接收结束,由用户自定义**/public LinkedList<Byte> getByteLinkedList(long spiltTimeLengthMS){final LinkedList<Byte> linkedListDataPool = new LinkedList<Byte>();threadTimeCount = new ThreadTimeCount();threadTimeCount.setEndTimeConut(spiltTimeLengthMS);threadReadData = new Thread(new Runnable(){private byte temp;@Overridepublic void run(){//加锁,以防函数返回空值synchronized (linkedListDataPool){try{//如果read返回值为-1或者isInterrupted()接收到结束信号则跳出循环while( ((temp = (byte) getInputStream().read()) != -1) && !threadReadData.isInterrupted()){linkedListDataPool.add(temp);//超过某个毫秒数没输入,就将链表抛出到变成数组,然后清空,再接收threadTimeCount.stillInputing();}}catch (IOException e){e.printStackTrace();}}//Log.i(TAG, "collect Byte Finished");}});threadTimeCount.threadBabySitting(threadReadData);threadTimeCount.start();synchronized (linkedListDataPool){return linkedListDataPool;}}/**以StringBuffer链表方式读取(主动捕获模式) * @param spiltTimeLengthMS 隔多久没收到数据就视为本次接收结束,由用户自定义**/public StringBuffer getStringBuffer(long spiltTimeLengthMS){StringBuffer buffer = new StringBuffer();for(byte temp : getByteLinkedList(spiltTimeLengthMS)) buffer.append((char)temp);return buffer;}/**以字节数组方式读取(主动捕获模式) * @param spiltTimeLengthMS 隔多久没收到数据就视为本次接收结束,由用户自定义**/public byte[] getByteArray(long spiltTimeLengthMS){LinkedList<Byte> bytes = getByteLinkedList(spiltTimeLengthMS);int position = 0;byte byteArray[] = new byte[bytes.size()];for(byte temp : bytes) byteArray[position++] = temp;return byteArray;}/**以字节数组方式输出到串口(主动捕获模式)**/public void outputByteArray(byte[] data) throws IOException{getOutputStream().write(data);getOutputStream().flush();}/**以字符串方式输出到串口(主动捕获模式)**/public void outputString(String data) throws IOException{outputByteArray(data.getBytes());}/**观察者模式,通过回调,检测到数据就调用用户自定义函数,被动捕获模式 * 强烈推荐使用观察者模式**/public abstract class SerialObserver{private LinkedList<Byte> linkedListSerialData = new LinkedList<Byte>();private long spiltTimeLengthMS;private long serialDataSize = 0;/** @param spiltTimeLengthMS 隔多久没收到数据就视为本次接收结束,由用户自定义**/public SerialObserver(long spiltTimeLengthMS){observerMode = true;SerialObserver.this.spiltTimeLengthMS = spiltTimeLengthMS;//数据采集线程:new Thread(new Runnable(){@Overridepublic void run(){byte temp = 0;try{if(getInputStream() == null){Log.e("SerialUtil", "读取窗口识别失败");return;}while( ((temp = (byte) getInputStream().read()) != -1) && observerMode){synchronized (linkedListSerialData){linkedListSerialData.add(temp);serialDataSize ++ ;}}}catch (IOException e) {e.printStackTrace();}}}).start();//停顿检查线程:new Thread(new Runnable(){@SuppressWarnings("unchecked")@Overridepublic void run(){while(observerMode){try{long oldSerialDataSize = serialDataSize;Thread.sleep(SerialObserver.this.spiltTimeLengthMS);if(oldSerialDataSize == serialDataSize){synchronized (linkedListSerialData){serialData((LinkedList<Byte>)linkedListSerialData.clone());}linkedListSerialData.clear();serialDataSize = 0;}}catch (InterruptedException e){e.printStackTrace();}}}}).start();}public void turnOffObserverMode(){observerMode = false;}public void turnOnObserverMode(){observerMode = true;}public abstract void serialData(LinkedList<Byte> data);}}


ThreadTimeCount:


package com.cjz.jniserialtest;public class ThreadTimeCount extends Thread{/**循环控制开关**/private boolean runSwitch = true;/**控制多少毫秒没数据流入就代表这次数据传入结束,默认100**/private long endTimeCountMs = 100;private long inputTimes;private Thread threadIntoMe = null;@Overridepublic void run(){while(runSwitch){long oldUpdateCount = inputTimes;try{Thread.sleep(endTimeCountMs);} catch (InterruptedException e) {e.printStackTrace();}if(inputTimes == oldUpdateCount){if(this.threadIntoMe != null) this.threadIntoMe.interrupt();close();}}}@Overridepublic void interrupt(){super.interrupt();close();}/**告诉线程其实我还在写数据**/public void stillInputing(){inputTimes ++;}public void close(){runSwitch = false;}/**设置多少毫秒没收到数据就代表已完成一次接收 * @param timeCount 毫米级时间**/public void setEndTimeConut(long timeCount){this.endTimeCountMs = timeCount;}/**线程托管**/public void threadBabySitting(Thread thread){this.threadIntoMe = thread;if(this.threadIntoMe != null) this.threadIntoMe.start();}}


CommonUtil:

public class CommUtils {    private static Activity currentActivity = null;    private static String suPath = "/system/xbin/su";    public static String getSuPath() {        return suPath;    }    public static void setSuPath(String suPath) {        CommUtils.suPath = suPath;    }}



使用方法示例(截取自己工程的一部分):

通过继承抽象类SerialUtil.SerialObserver,splitTimeLengthMS代表超过多少毫秒没有新字节发过来,就当是接受完了一份数据,默认是100ms的间隔时间。然后内容会以Byte链表的形式回调给serialData方法,这里写自己要用的代码即可。例子代码的是我自己安卓工程的一个内部类,把收到的卡号数据(小端)整理到一个long变量中,然后给打卡卡号处理方法进一步处理。就像流水线一样。


   public NormalModeController(ModeActivity act) {//其他过程............class SerialReader extends SerialUtil.SerialObserver {public SerialReader(SerialUtil serialUtil, long spiltTimeLengthMS) {serialUtil.super(spiltTimeLengthMS);}@Overridepublic void serialData(LinkedList<Byte> data) {long cardNumber = 0;String strCardNumber = null;if(data == null) return;if(data.size() < 4) return;LogUtils.logI("serialData:" +   String.format("%X", (data.get(0) & 0xFF)) + "," +String.format("%X", (data.get(1) & 0xFF)) + "," +String.format("%X", (data.get(2) & 0xFF)) + "," +String.format("%X", (data.get(3) & 0xFF)));//方法1:/*cardNumber = cardNumber | ((data.get(3) & 0xFF) << 24);cardNumber = cardNumber | ((data.get(2) & 0xFF) << 16);cardNumber = cardNumber | ((data.get(1) & 0xFF) << 8);cardNumber = cardNumber | ((data.get(0) & 0xFF) << 0);*///方法2:抽象成了一个循环搞定(注意是小端数据格式,所以要反向循环。因为超过了4字节,怕影响符号位,所以用long来保存位移数)for(int i = data.size() - 1; i >= 0; i--){cardNumber = cardNumber | (data.get(i) & 0xFF);if(i > 0) cardNumber = cardNumber << 8;}if(cardNumber == 0) return ;//将其定长为10位整数:strCardNumber = String.format("%010d", cardNumber);Log.i("Content:", strCardNumber);//把卡号发送到主线程Message msg = new Message();msg.what = NormalModeControllerConstant.SEND_CARD_MSG;msg.obj = strCardNumber;handler.sendMessage(msg);}}//创建SerialUtil对象,传入串口设备号,还有波特率。SerialUtil serialUtil = new SerialUtil("/dev/ttyS3", SerialUtil.BaudRateList.B9600);//创建继承抽象类后实现了serialData方法的SerialReader类对象,传入serialUtil打通上下文,设50ms为分割时间SerialReader serialReader = new SerialReader(serialUtil, 50);//开启观察者模式(回调数据模式)serialReader.turnOnObserverMode();    }


注意Gradle的App脚本要加点代码(abiFilters可以自己增删指令集类型)


Android.mk内容如下:


LOCAL_PATH := $(call my-dir)include $(CLEAR_VARS)LOCAL_MODULE    := JNISerialCtrlLOCAL_SRC_FILES := SerialCtrl.cinclude $(BUILD_SHARED_LIBRARY)


找到你放SerialCtrl.c的文件夹,然后在里面输入ndk-build,把得到的各种如以armeabi为名的so文件夹,复制到libs文件夹即可。但SerialCtrl.c可以不必放入安卓工程中,只要编译好的库文件夹放进去就行。