最近公司要做一个展示牌形式的打卡器,Android系统且展示牌底部有个串口来接收大家手环打卡出勤的信息,这就需要读取每个手环或者工牌的id信息,因此用到了Android studio自带的jni功能在这里做一下总结笔记,方便以后直接拉下来使用.
- 项目创建时如图,这样会包含native-lib.cpp文件,此文件是主要串口开发的C++函数,包含打开串口/串口设置/关闭串口等的设置

- native-lib.cpp文件
#include <jni.h>
#include <string>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <stdint.h>
#include <termios.h>
#include <sys/ioctl.h>
#include <android/log.h>
static const char *TAG="serial_port";
#define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, TAG, fmt, ##args)
#define LOGI(fmt, args...) __android_log_print(ANDROID_LOG_INFO, TAG, fmt, ##args)
#define LOGE(fmt, args...) __android_log_print(ANDROID_LOG_ERROR, TAG, fmt, ##args)
extern "C" JNIEXPORT jstring
JNICALL
Java_com_jetsen_joy_carddemo_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
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;
}
}
extern "C"
JNIEXPORT jobject JNICALL
Java_com_jetsen_joy_carddemo_SerialPort_open(JNIEnv *env, jclass thiz, jstring path,
jint baudrate) {
int fd;
speed_t speed;
jobject mFileDescriptor;
/* 这里获取设置的波特率 */
{
speed = getBaudrate(baudrate);
if (speed == -1) {
/* TODO: throw an exception */
LOGE("Invalid baudrate");
return NULL;
}
}
/* 打开串口 */
{
jboolean iscopy;
const char *path_utf = env->GetStringUTFChars(path, &iscopy);
// LOGD("Opening serial port %s with flags 0x%x", path_utf, O_RDWR | flags);
// fd = open(path_utf, O_RDWR | flags);
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");
/* TODO: throw an exception */
return NULL;
}
}
/*配置串口 */
{
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);// 结构中存储的输入波特率为 speed。如果输入波特率被设为0,实际输入波特率将等于输出波特率
cfsetospeed(&cfg, speed);//设置 termios_p 指向的 termios 结构中存储的输出波特率为 speed
if (tcsetattr(fd, TCSANOW, &cfg))//设置串口的配置,TCSANOW立即生效的意思
{
LOGE("tcsetattr() failed");
close(fd);
/* TODO: throw an exception */
return NULL;
}
}
/* 生成一个文件描述符FileDescriptor*/
{
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;
}
extern "C"
JNIEXPORT void JNICALL
Java_com_jetsen_joy_carddemo_SerialPort_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);
}
- 新建一个SerialPort类,此类中主要是open close串口方法,创建此两个本地方法,运行时Android会自动调用jni中对应的方法,从而实现对指定串口的打开及关闭;
public class SerialPort {
private final String TAG=SerialPort.class.getName();
private FileDescriptor mFd;
private FileInputStream mFileInputStream;
private FileOutputStream mFileOutputStream;
public SerialPort(File device, int baudrate) throws SecurityException, IOException {
/* 这里是判断有串口的读写权限 */
if (!device.canRead() || !device.canWrite()) {
try {
/*没有就需要申请权限,这个地方需要将Android设备root提供权限 */
Process su;
su = Runtime.getRuntime().exec("/system/bin/su");
String cmd = "chmod 666 " + device.getAbsolutePath() + "\n"
+ "exit\n";
su.getOutputStream().write(cmd.getBytes());
if ((su.waitFor() != 0) || !device.canRead()
|| !device.canWrite()) {
throw new SecurityException();
}
} catch (Exception e) {
e.printStackTrace();
throw new SecurityException();
}
}
//调用jni,打开串口
this.mFd = open(device.getAbsolutePath(), baudrate);
if(this.mFd == null) {
Log.e(TAG, "native open returns null");
throw new IOException();
} else {
//获取与串口通信的输入输出流
this.mFileInputStream = new FileInputStream(this.mFd);
this.mFileOutputStream = new FileOutputStream(this.mFd);
}
}
// Getters and setters
public InputStream getInputStream() {
return mFileInputStream;
}
public OutputStream getOutputStream() {
return mFileOutputStream;
}
static {
//这个静态库的名字必须与之前我们在CMakeLists.txt中的命名相同
System.loadLibrary("native-lib");
}
// JNI
private native static FileDescriptor open(String path, int baudrate);
public native void close();
}
- SerialPortUtil串口工具类 ,对串口的打开,关闭,接收数据进行处理
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/ttyS3";//不固定,我这里硬件设备读取手环信息的串口是ttyS3
private int baudrate = 9600;//不固定,波特率 串口需要同设备提供商要到信息
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;
}
/**
* 初始化串口通信
*/
private 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();
}
}
/**
* 循环读取手环信息
*/
private class ReadThread extends Thread {
@Override
public void run() {
super.run();
while (!isStop && !isInterrupted()) {
int size;
try {
if (mInputStream == null)
return;
if (mInputStream.available() > 0){
byte[] buffer = new byte[mInputStream.available()];
size = mInputStream.read(buffer);
if (size > 0) {
if (null != onDataReceiveListener) {
onDataReceiveListener.onDataReceive(buffer, size);
}
}
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
return;
}
}
}
}
/**
* 关闭串口
*/
public void closeSerialPort() {
isStop = true;
if (mReadThread != null) {
mReadThread.interrupt();
}
if (mSerialPort != null) {
mSerialPort.close();
}
}
}
- MainActivty类中做读取到数据的处理
public class MainActivity extends AppCompatActivity {
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib");
}
TextView tv;
SerialPortUtil mSerialPortUtil;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Example of a call to a native method
tv = (TextView) findViewById(R.id.sample_text);
tv.setText(stringFromJNI());
mSerialPortUtil = SerialPortUtil.getInstance();
mSerialPortUtil.setOnDataReceiveListener(new SerialPortUtil.OnDataReceiveListener() {
@Override
public void onDataReceive(final byte[] buffer, final int size) {
runOnUiThread(new Runnable() {
@Override
public void run() {
//将读取到的数据进行处理
tv.setText("读到了size:" + size + "--" + new String(buffer) + "end");
Toast.makeText(MainActivity.this,
"收到了size:" + size + "--" + new String(buffer) + "end",
Toast.LENGTH_LONG).show();
}
});
}
});
}
/**
* A native method that is implemented by the 'native-lib' native library,
* which is packaged with this application.
*/
public native String stringFromJNI();
}
- 添加权限
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.CALL_PHONE" />
本文介绍了一种在Android平台上实现串口通信的具体方法,通过JNI技术实现C++底层与Java层交互,完成串口的打开、配置、读取数据等功能,并提供了一个完整的串口通信示例。
723

被折叠的 条评论
为什么被折叠?



