在不久前,接触到了物联网的开发,当时一脸懵逼了,后来问了度娘和有幸遇到大神的指导,最终实现了功能。
首先先弄懂跟硬件交互时的一些协议(这协议都是硬件公司提供的),就在jni直接进行串口设备的读写,而且Android使用jni直接进行串口设备的读写网上已经有开源项目了,本文是基于网上的开源项目在实际项目中的使用做的调整和优化;
Google串口开源项目见:https://code.google.com/p/android-serialport-api/
下面是我项目中的相关代码及介绍:(因GitHub账号最近有些问题,只能直接把代码贴上来)
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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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)
staticspeed_t getBaudrate(jint baudrate) {
switch(baudrate) {
case0:
returnB0;
case50:
returnB50;
case75:
returnB75;
case110:
returnB110;
case134:
returnB134;
case150:
returnB150;
case200:
returnB200;
case300:
returnB300;
case600:
returnB600;
case1200:
returnB1200;
case1800:
returnB1800;
case2400:
returnB2400;
case4800:
returnB4800;
case9600:
returnB9600;
case19200:
returnB19200;
case38400:
returnB38400;
case57600:
returnB57600;
case115200:
returnB115200;
case230400:
returnB230400;
case460800:
returnB460800;
case500000:
returnB500000;
case576000:
returnB576000;
case921600:
returnB921600;
case1000000:
returnB1000000;
case1152000:
returnB1152000;
case1500000:
returnB1500000;
case2000000:
returnB2000000;
case2500000:
returnB2500000;
case3000000:
returnB3000000;
case3500000:
returnB3500000;
case4000000:
returnB4000000;
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) {
intfd;
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");
returnNULL;
}
}
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 */
returnNULL;
}
}
LOGD("init native Configure device!");
/* Configure device */
{
struct termios cfg;
if(tcgetattr(fd,&cfg)) {
LOGE("Configure device tcgetattr() failed 1");
close(fd);
returnNULL;
}
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 */
returnNULL;
}
}
/* Create a corresponding file descriptor */
{
jclass cFileDescriptor = env->FindClass("java/io/FileDescriptor");
jmethodID iFileDescriptor = env->GetMethodID(cFileDescriptor,"","()V");
jfieldID descriptorID = env->GetFieldID(cFileDescriptor,"descriptor","I");
mFileDescriptor = env->NewObject(cFileDescriptor,iFileDescriptor);
env->SetIntField(mFileDescriptor,descriptorID,(jint) fd);
}
returnmFileDescriptor;
}
/*
* 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);
return1;
}
staticJNINativeMethod gMethods[] = {
{"open","(Ljava/lang/String;I)Ljava/io/FileDescriptor;",(void*) native_open },
{"close","()I",(void*) native_close },
};
/*
*为某一个类注册本地方法
*/
static intregisterNativeMethods(JNIEnv* env, const char* className,
JNINativeMethod* gMethods, intnumMethods) {
jclass clazz;
clazz = env->FindClass(className);
if(clazz == NULL) {
returnJNI_FALSE;
}
if(env->RegisterNatives(clazz,gMethods,numMethods) <0) {
returnJNI_FALSE;
}
returnJNI_TRUE;
}
/*
*为所有类注册本地方法
*/
static intregisterNatives(JNIEnv* env) {
const char* kClassName ="com/jerome/serialport/SerialPort";//指定要注册的类
returnregisterNativeMethods(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;
returnresult;
}
在编译时注意修改const char* kClassName = "com/jerome/serialport/SerialPort";为你Java层与jni对应得包名;
2、Android.mk
LOCAL_PATH :=$(call my-dir)
include$(CLEAR_VARS)
TARGET_PLATFORM := android-3
LOCAL_MODULE:= serial_port
LOCAL_SRC_FILES := SerialPort.cpp
LOCAL_LDLIBS:= -llog
include$(BUILD_SHARED_LIBRARY)
如果要修改生成so文件的名称,请修改LOCAL_MODULE := serial_port
3、SerialPort.java
importjava.io.File;
importjava.io.FileDescriptor;
importjava.io.FileInputStream;
importjava.io.FileOutputStream;
importjava.io.IOException;
importjava.io.InputStream;
importjava.io.OutputStream;
public classSerialPort {
private static finalStringTAG="SerialPort";
/*
* Do not remove or rename the field mFd: it is used by native method close();
*/
privateFileDescriptormFd;
privateFileInputStreammFileInputStream;
privateFileOutputStreammFileOutputStream;
publicSerialPort(File device, intbaudrate)throwsSecurityException,IOException {
mFd= open(device.getAbsolutePath(),baudrate);
if(mFd==null) {
throw newIOException();
}
mFileInputStream=newFileInputStream(mFd);
mFileOutputStream=newFileOutputStream(mFd);
}
publicInputStreamgetInputStream() {
returnmFileInputStream;
}
publicOutputStreamgetOutputStream() {
returnmFileOutputStream;
}
private nativeFileDescriptoropen(String path, intbaudrate);
public native intclose();
static{
System.loadLibrary("serial_port");
}
}
4、SerialPortUtil.java
importjava.io.BufferedWriter;
importjava.io.File;
importjava.io.IOException;
importjava.io.InputStream;
importjava.io.OutputStream;
importjava.io.OutputStreamWriter;
importjava.io.PrintWriter;
/**
*串口操作类
*
*@authorJerome
*
*/
public classSerialPortUtil {
privateStringTAG= SerialPortUtil.class.getSimpleName();
privateSerialPortmSerialPort;
privateOutputStreammOutputStream;
privateInputStreammInputStream;
privateReadThreadmReadThread;
privateStringpath="/dev/ttyMT1";
private intbaudrate=115200;
private staticSerialPortUtilportUtil;
privateOnDataReceiveListeneronDataReceiveListener=null;
private booleanisStop=false;
public interfaceOnDataReceiveListener {
public voidonDataReceive(byte[] buffer, intsize);
}
public voidsetOnDataReceiveListener(
OnDataReceiveListener dataReceiveListener) {
onDataReceiveListener= dataReceiveListener;
}
public staticSerialPortUtilgetInstance() {
if(null==portUtil) {
portUtil=newSerialPortUtil();
portUtil.onCreate();
}
returnportUtil;
}
/**
*初始化串口信息
*/
public voidonCreate() {
try{
mSerialPort=newSerialPort(newFile(path),baudrate);
mOutputStream=mSerialPort.getOutputStream();
mInputStream=mSerialPort.getInputStream();
mReadThread=newReadThread();
isStop=false;
mReadThread.start();
}catch(Exception e) {
e.printStackTrace();
}
initBle();
}
/**
*发送指令到串口
*
*@paramcmd
*@return
*/
public booleansendCmds(String cmd) {
booleanresult =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;
}
returnresult;
}
public booleansendBuffer(byte[] mBuffer) {
booleanresult =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;
}
returnresult;
}
private classReadThreadextendsThread {
@Override
public voidrun() {
super.run();
while(!isStop&& !isInterrupted()) {
intsize;
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:"+newString(buffer,0,size));
}
if(null!=onDataReceiveListener) {
onDataReceiveListener.onDataReceive(buffer,size);
}
}
Thread.sleep(10);
}catch(Exception e) {
e.printStackTrace();
return;
}
}
}
}
/**
*关闭串口
*/
public voidcloseSerialPort() {
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即可接受数据;
6、温馨提醒:在使用流的时候记得要及时关闭流,没有关闭的话因为流资源就会被他一直占用,这样别人想用就没有办法用了,所以这回造成资源浪费,而且要关闭资源,最好写在finally中,如果这个东西出现一个异常,你就关不掉了。