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); }
记得开一个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; @Override public 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() { @Override public 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") @Override public 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; @Override public 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(); } } } @Override public 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(); } }
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); } @Override public 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 := JNISerialCtrl LOCAL_SRC_FILES := SerialCtrl.c include $(BUILD_SHARED_LIBRARY)
找到你放SerialCtrl.c的文件夹,然后在里面输入ndk-build,把得到的各种如以armeabi为名的so文件夹,复制到libs文件夹即可。但SerialCtrl.c可以不必放入安卓工程中,只要编译好的库文件夹放进去就行。