在Android串口通信:基本知识梳理(http://gqdy365.iteye.com/admin/blogs/2188846)的基础上,我结合我项目中使用串口的实例,进行总结;
Android使用jni直接进行串口设备的读写网上已经有开源项目了,本文是基于网上的开源项目在实际项目中的使用做的调整和优化;
Google串口开源项目见:https://code.google.com/p/android-serialport-api/
下面是我项目中的相关代码及介绍:
1、SerialPort.cpp
/* * Copyright 2009 Cedric Priscal * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */#include <stdlib.h>#include <stdio.h>#include <jni.h>#include <assert.h>#include <termios.h>#include <unistd.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <string.h>#include <jni.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: cedric_serial_SerialPort * Method: open * Signature: (Ljava/lang/String;)V */JNIEXPORT jobject JNICALL native_open(JNIEnv *env, jobject thiz, jstring path,jint baudrate) { int fd; speed_t speed; jobject mFileDescriptor; LOGD("init native Check arguments"); /* Check arguments */ { speed = getBaudrate(baudrate); if (speed == -1) { /* TODO: throw an exception */ LOGE("Invalid baudrate"); return NULL; } } LOGD("init native Opening device!"); /* Opening device */ { jboolean iscopy; const char *path_utf = env->GetStringUTFChars(path, &iscopy); LOGD("Opening serial port %s", path_utf);// fd = open(path_utf, O_RDWR | O_DIRECT | O_SYNC); fd = open(path_utf, O_RDWR | O_NOCTTY | O_NONBLOCK | O_NDELAY); LOGD("open() fd = %d", fd); env->ReleaseStringUTFChars(path, path_utf); if (fd == -1) { /* Throw an exception */ LOGE("Cannot open port %d",baudrate); /* TODO: throw an exception */ return NULL; } } LOGD("init native Configure device!"); /* Configure device */ { struct termios cfg; if (tcgetattr(fd, &cfg)) { LOGE("Configure device tcgetattr() failed 1"); close(fd); return NULL; } cfmakeraw(&cfg); cfsetispeed(&cfg, speed); cfsetospeed(&cfg, speed); if (tcsetattr(fd, TCSANOW, &cfg)) { LOGE("Configure device tcsetattr() failed 2"); close(fd); /* TODO: throw an exception */ return NULL; } } /* Create a corresponding file descriptor */ { jclass cFileDescriptor = env->FindClass("java/io/FileDescriptor"); jmethodID iFileDescriptor = env->GetMethodID(cFileDescriptor,"<init>", "()V"); jfieldID descriptorID = env->GetFieldID(cFileDescriptor,"descriptor", "I"); mFileDescriptor = env->NewObject(cFileDescriptor,iFileDescriptor); env->SetIntField(mFileDescriptor, descriptorID, (jint) fd); } return mFileDescriptor;}/* * Class: cedric_serial_SerialPort * Method: close * Signature: ()V */JNIEXPORT jint JNICALL native_close(JNIEnv * env, jobject thiz){ jclass SerialPortClass = env->GetObjectClass(thiz); jclass FileDescriptorClass = env->FindClass("java/io/FileDescriptor"); jfieldID mFdID = env->GetFieldID(SerialPortClass, "mFd", "Ljava/io/FileDescriptor;"); jfieldID descriptorID = env->GetFieldID(FileDescriptorClass, "descriptor", "I"); jobject mFd = env->GetObjectField(thiz, mFdID); jint descriptor = env->GetIntField(mFd, descriptorID); LOGD("close(fd = %d)", descriptor); close(descriptor); return 1;}static JNINativeMethod gMethods[] = { { "open", "(Ljava/lang/String;I)Ljava/io/FileDescriptor;",(void*) native_open }, { "close", "()I",(void*) native_close },};/* * 为某一个类注册本地方法 */static int registerNativeMethods(JNIEnv* env, const char* className, JNINativeMethod* gMethods, int numMethods) { jclass clazz; clazz = env->FindClass(className); if (clazz == NULL) { return JNI_FALSE; } if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) { return JNI_FALSE; } return JNI_TRUE;}/* * 为所有类注册本地方法 */static int registerNatives(JNIEnv* env) { const char* kClassName = "com/jerome/serialport/SerialPort"; //指定要注册的类 return registerNativeMethods(env, kClassName, gMethods, sizeof(gMethods) / sizeof(gMethods[0]));}/* * System.loadLibrary("lib")时调用 * 如果成功返回JNI版本, 失败返回-1 */JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env = NULL; jint result = -1; if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) { return -1; } assert(env != NULL); if (!registerNatives(env)) { //注册 return -1; } //成功 result = JNI_VERSION_1_4; return result;}
在编译时注意修改const char* kClassName = "com/jerome/serialport/SerialPort";为你Java层与jni对应得包名;
2、Android.mk
LOCAL_PATH := $(call my-dir)include $(CLEAR_VARS)TARGET_PLATFORM := android-3LOCAL_MODULE := serial_portLOCAL_SRC_FILES := SerialPort.cppLOCAL_LDLIBS := -lloginclude $(BUILD_SHARED_LIBRARY)
如果要修改生成so文件的名称,请修改LOCAL_MODULE := serial_port
3、SerialPort.java
package com.jerome.serialport;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.OutputStream;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) throws SecurityException, IOException { mFd = open(device.getAbsolutePath(), baudrate); if (mFd == null) { throw new IOException(); } mFileInputStream = new FileInputStream(mFd); mFileOutputStream = new FileOutputStream(mFd); } public InputStream getInputStream() { return mFileInputStream; } public OutputStream getOutputStream() { return mFileOutputStream; } private native FileDescriptor open(String path, int baudrate); public native int close(); static { System.loadLibrary("serial_port"); }}
4、SerialPortUtil.java
package com.jerome.serialport;import java.io.BufferedWriter;import java.io.File;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import java.io.OutputStreamWriter;import java.io.PrintWriter;/** * 串口操作类 * * @author Jerome * */public class SerialPortUtil { private String TAG = SerialPortUtil.class.getSimpleName(); private SerialPort mSerialPort; private OutputStream mOutputStream; private InputStream mInputStream; private ReadThread mReadThread; private String path = "/dev/ttyMT1"; private int baudrate = 115200; private static SerialPortUtil portUtil; private OnDataReceiveListener onDataReceiveListener = null; private boolean isStop = false; public interface OnDataReceiveListener { public void onDataReceive(byte[] buffer, int size); } public void setOnDataReceiveListener( OnDataReceiveListener dataReceiveListener) { onDataReceiveListener = dataReceiveListener; } public static SerialPortUtil getInstance() { if (null == portUtil) { portUtil = new SerialPortUtil(); portUtil.onCreate(); } return portUtil; } /** * 初始化串口信息 */ public void onCreate() { try { mSerialPort = new SerialPort(new File(path), baudrate); mOutputStream = mSerialPort.getOutputStream(); mInputStream = mSerialPort.getInputStream(); mReadThread = new ReadThread(); isStop = false; mReadThread.start(); } catch (Exception e) { e.printStackTrace(); } initBle(); } /** * 发送指令到串口 * * @param cmd * @return */ public boolean sendCmds(String cmd) { boolean result = true; byte[] mBuffer = (cmd+"\r\n").getBytes();//注意:我得项目中需要在每次发送后面加\r\n,大家根据项目项目做修改,也可以去掉,直接发送mBuffer try { if (mOutputStream != null) { mOutputStream.write(mBuffer); } else { result = false; } } catch (IOException e) { e.printStackTrace(); result = false; } return result; } public boolean sendBuffer(byte[] mBuffer) { boolean result = true; String tail = "\r\n"; byte[] tailBuffer = tail.getBytes(); byte[] mBufferTemp = new byte[mBuffer.length+tailBuffer.length]; System.arraycopy(mBuffer, 0, mBufferTemp, 0, mBuffer.length); System.arraycopy(tailBuffer, 0, mBufferTemp, mBuffer.length, tailBuffer.length);//注意:我得项目中需要在每次发送后面加\r\n,大家根据项目项目做修改,也可以去掉,直接发送mBuffer try { if (mOutputStream != null) { mOutputStream.write(mBufferTemp); } else { result = false; } } catch (IOException e) { e.printStackTrace(); result = false; } return result; } private class ReadThread extends Thread { @Override public void run() { super.run(); while (!isStop && !isInterrupted()) { int size; try { if (mInputStream == null) return; byte[] buffer = new byte[512]; size = mInputStream.read(buffer); if (size > 0) { if(MyLog.isDyeLevel()){ MyLog.log(TAG, MyLog.DYE_LOG_LEVEL, "length is:"+size+",data is:"+new String(buffer, 0, size)); } if (null != onDataReceiveListener) { onDataReceiveListener.onDataReceive(buffer, size); } } Thread.sleep(10); } catch (Exception e) { e.printStackTrace(); return; } } } } /** * 关闭串口 */ public void closeSerialPort() { sendShellCommond1(); isStop = true; if (mReadThread != null) { mReadThread.interrupt(); } if (mSerialPort != null) { mSerialPort.close(); } } }
5、使用方法:
a、配置ndk开发环境,具体百度一下;
b、工程根目录下新建jni文件夹,将Android.mk和SerialPort.cpp放进去;
c、ndk中进入jni目录,编译生成so文件,默认so生成在libs/armeabi下;
d、新建com.jerom.serialport目录,将SerialPort和SerialPortUtil放进去;
f、在你要使用的地方初始化SerialPortUtil,实现回调接口OnDataReceiveListener即可接受数据;
总结:
1、串口发送实质就是向串口设备(类似于文件操作)写入字节流,串口读取也是一样;
2、主要jni与Java native得对应;