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

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;
			@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();

	}
}


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);
				}

				@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可以不必放入安卓工程中,只要编译好的库文件夹放进去就行。

猜你喜欢

转载自blog.csdn.net/cjzjolly/article/details/78731906