在Android串口通信:基本知识梳理的基础上,我结合我项目中使用串口的实例,进行总结;

Android使用jni直接进行串口设备的读写网上已经有开源项目了,本文是基于网上的开源项目在实际项目中的使用做的调整和优化;
Google串口开源项目

下面是我项目中的相关代码及介绍:

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-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

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得对应;

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

01-30 18:00