C代码如下:
#include
#include
#include
#include
#include
#include
#include
//#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, "", "()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 getByteLinkedList(long spiltTimeLengthMS)
{
final LinkedList linkedListDataPool = new LinkedList();
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() & 0xFF) != -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 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 linkedListSerialData = new LinkedList();
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)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 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 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();
}
Android.mk内容如下:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := JNISerialCtrl
LOCAL_SRC_FILES := SerialCtrl.c
include $(BUILD_SHARED_LIBRARY)