> 文档中心 > Android利用CMake进行JNI串口操作

Android利用CMake进行JNI串口操作

利用CMake进行JNI串口操作

    • 三方应用的操作串口方式
    • 系统应用操作串口的方式
    • 串口设备

三方应用的操作串口方式

由于三方应用无法通过系统的串口API操作串口,需要借助JNI访问系统串口节点。
步骤如下:
新建Android Native LIbrary,生成CMakeLists.txt和SerialPort.cpp。
Android利用CMake进行JNI串口操作
Android利用CMake进行JNI串口操作

CMakeLists.txt如下:

# For more information about using CMake with Android Studio, read the# documentation: https://d.android.com/studio/projects/add-native-code.html# Sets the minimum version of CMake required to build the native library.cmake_minimum_required(VERSION 3.18.1)# Declares and names the project.project("serialport")# Creates and names a library, sets it as either STATIC# or SHARED, and provides the relative paths to its source code.# You can define multiple libraries, and CMake builds them for you.# Gradle automatically packages shared libraries with your APK.add_library( # Sets the name of the library. serialport # Sets the library as a shared library. SHARED # Provides a relative path to your source file(s). SerialPort.cpp)# Searches for a specified prebuilt library and stores the path as a# variable. Because CMake includes system libraries in the search path by# default, you only need to specify the name of the public NDK library# you want to add. CMake verifies that the library exists before# completing its build.find_library( # Sets the name of the path variable. log-lib # Specifies the name of the NDK library that # you want CMake to locate. log)# Specifies libraries CMake should link to your target library. You# can link multiple libraries, such as libraries you define in this# build script, prebuilt third-party libraries, or system libraries.target_link_libraries( # Specifies the target library. serialport # Links the target library to the log library # included in the NDK. ${log-lib})

编写串口的c++代码,操作系统API的方式,SerialPort.cpp代码如下:

#include #include #include #include #include #include #include #include #include "android/log.h"static const char *TAG = "serial_port";jmethodID FindMethod(JNIEnv *env, jclass klass, const char *name, const char *signature);jfieldID FindField(JNIEnv *pEnv, jclass klass, const char *name, const char *desc);#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 getBandRate(jint bandRate) {    switch (bandRate) { 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 jint JNICALL Java_com_example_serialport_SerialPort_open (JNIEnv *env, jobject thiz, jstring path, jint baudrate, jint flags) {    int fd;    speed_t speed;    jobject fileDescriptor;    /* Check arguments */    { speed = getBandRate(baudrate); if (speed == -1) {     return -1; }    }    /* Opening device */    { jboolean is_copy; const char *path_utf = (*env).GetStringUTFChars(path, &is_copy); 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(path, path_utf); if (fd == -1) {     /* Throw an exception */     LOGE("Cannot open port");     return -1; }    }    /* Configure device */    { struct termios cfg{}; LOGD("Configuring serial port"); if (tcgetattr(fd, &cfg)) {     close(fd);     return -1; } cfmakeraw(&cfg); cfsetispeed(&cfg, speed); cfsetospeed(&cfg, speed); if (tcsetattr(fd, TCSANOW, &cfg)) {     close(fd);     return -1; }    }    /* Create a corresponding file descriptor */    { jclass fileDescriptorClass = (*env).FindClass("java/io/FileDescriptor"); jmethodID fileDescriptorInitMethod = FindMethod(env, fileDescriptorClass, "", "()V"); jfieldID fileDescriptorDescriptorField = FindField(env, fileDescriptorClass, "descriptor",   "I"); fileDescriptor = (*env).NewObject(fileDescriptorClass, fileDescriptorInitMethod); (*env).SetIntField(fileDescriptor, fileDescriptorDescriptorField, (jint) fd);    }    return fd;}jmethodID FindMethod(JNIEnv *env, jclass klass, const char *name, const char *signature) {    jmethodID result = env->GetMethodID(klass, name, signature);    if (result == nullptr) { abort();    }    return result;}jfieldID FindField(JNIEnv *env, jclass klass, const char *name, const char *desc) {    jfieldID result = env->GetFieldID(klass, name, desc);    if (result == nullptr) { abort();    }    return result;}extern "C" JNIEXPORT void JNICALL Java_com_example_serialport_SerialPort_close (JNIEnv *env, jobject thiz, jint fd) {    close(fd);}extern "C" JNIEXPORT jint JNICALL Java_com_example_serialport_SerialPort_readArray (JNIEnv *env, jobject thiz, jint fd, jbyteArray buffer, jint length) {    auto *buf = (jbyte *) malloc(length);    if (!buf) { LOGE("jniThrowException java/lang/OutOfMemoryError"); return -1;    }    int ret = read(fd, buf, length);    if (ret > 0) { // copy data from native buffer to Java buffer env->SetByteArrayRegion(buffer, 0, ret, buf);    }    free(buf);    if (ret < 0) LOGE("jniThrowException java/io/IOException");    return ret;}extern "C" JNIEXPORT jint JNICALL Java_com_example_serialport_SerialPort_readDirect (JNIEnv *env, jobject thiz, jint fd, jobject buffer, jint length) {    auto *buf = (jbyte *) env->GetDirectBufferAddress(buffer);    if (!buf) { LOGE("ByteBuffer not direct: jniThrowException java/lang/IllegalArgumentException"); return -1;    }    int ret = read(fd, buf, length);    if (ret < 0) LOGE("jniThrowException java/io/IOException");    return ret;}extern "C" JNIEXPORT void JNICALL Java_com_example_serialport_SerialPort_writeArray (JNIEnv *env, jobject thiz, jint fd, jbyteArray byteArray, jint length) {    auto *buf = (jbyte *) malloc(length);    if (!buf) { LOGE("jniThrowException java/lang/OutOfMemoryError");    }    env->GetByteArrayRegion(byteArray, 0, length, buf);    jint ret = write(fd, buf, length);    free(buf);    if (ret < 0) LOGE("jniThrowException java/io/IOException");}extern "C" JNIEXPORT void JNICALL Java_com_example_serialport_SerialPort_writeDirect (JNIEnv *env, jobject thiz, jint fd, jobject buffer, jint length) {    auto *buf = (jbyte *) env->GetDirectBufferAddress(buffer);    if (!buf) { LOGE("ByteBuffer not direct: jniThrowException java/lang/IllegalArgumentException"); return;    }    int ret = write(fd, buf, length);    if (ret < 0) LOGE("jniThrowException java/io/IOException");}

在java依赖c++库,这里用的是JNI静态注册System.loadLibrary,定义Native方法,与c++代码中对应,SerialPort.java代码如下:

package com.example.serialport;import java.io.IOException;import java.nio.ByteBuffer;public class SerialPort {    static { System.loadLibrary("serialport");    }    private int fd = -1;    public void openSerialPort(String path, int bandRate, int flags) { fd = open(path, bandRate, flags);    }    public void closeSerialPort() { close(fd);    }    public int read(ByteBuffer buffer) throws IOException { if (fd < 0) {     return -1; } if (buffer.isDirect()) {     return readDirect(fd, buffer, buffer.remaining()); } else if (buffer.hasArray()) {     return readArray(fd, buffer.array(), buffer.remaining()); } else {     throw new IllegalArgumentException("buffer is not direct and has no array"); }    }    public void write(ByteBuffer buffer, int length) throws IOException { if (fd < 0) {     return; } if (buffer.isDirect()) {     writeDirect(fd, buffer, length); } else {     if (!buffer.hasArray()) {  throw new IllegalArgumentException("buffer is not direct and has no array");     }     writeArray(fd, buffer.array(), length); }    }    /**     * A native method that is implemented by the 'serialport' native library,     * which is packaged with this application.     */    private native int open(String path, int bandRate, int flags);    private native void close(int fd);    private native int readArray(int fd, byte[] buffer, int flags) throws IOException;    private native int readDirect(int fd, ByteBuffer buffer, int flags) throws IOException;    private native void writeArray(int fd, byte[] buffer, int flags) throws IOException;    private native void writeDirect(int fd, ByteBuffer buffer, int flags) throws IOException;}

接下来对SerialPort的封装,SerialPortManager继承Thread,同时需要检查串口节点的读写权限。代码如下:

package com.example.serialport;import android.util.Log;import java.io.File;import java.io.IOException;import java.nio.ByteBuffer;public class SerialPortManager extends Thread {    private static final String TAG = SerialPortManager.class.getSimpleName();    private static final int DATA_BUFFER_SIZE = 512; //最大字节长度    private final SerialPort mSerialPort;    private final ByteBuffer mInputBuffer;    private final ByteBuffer mOutputBuffer;    private SerialPortDataListener mSerialPortDataListener;    public SerialPortManager() { mSerialPort = new SerialPort(); mInputBuffer = ByteBuffer.allocate(DATA_BUFFER_SIZE); mOutputBuffer = ByteBuffer.allocate(DATA_BUFFER_SIZE);    }    public void openSerialPort(String name, int speed) { if (name != null && mSerialPort != null && checkAccessPermission(name)) {     mSerialPort.openSerialPort(name, speed, 0); }    }    /* Check access permission */    private boolean checkAccessPermission(String name) { File device = new File(name); if (!device.canRead() || !device.canWrite()) {     try {  /* Missing read/write permission, trying to chmod the file */  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();     } } return true;    }    public void closeSerialPort() { if (mSerialPort != null) {     mSerialPort.closeSerialPort(); }    }    @Override    public void run() { int ret = 0; byte[] buffer; while (ret >= 0 && mSerialPort != null) {     try {  mInputBuffer.clear();  ret = mSerialPort.read(mInputBuffer);     } catch (IOException e) {  e.printStackTrace();  break;     }     if (ret > 0 && mSerialPortDataListener != null) {  buffer = new byte[ret];  mInputBuffer.get(buffer, 0, ret);  mSerialPortDataListener.onDataRead(buffer);     } }    }    public void writeData(byte[] data) { if (mSerialPort != null && data.length > 0 && data.length <= DATA_BUFFER_SIZE) {     try {  mOutputBuffer.clear();  mOutputBuffer.put(data);  mSerialPort.write(mOutputBuffer, data.length);     } catch (IOException e) {  e.printStackTrace();     } } else {     Log.e(TAG, "writeDataToRemote failed"); }    }    public void setSerialPortDataListener(SerialPortDataListener listener) { if (listener != null) {     this.start();     mSerialPortDataListener = listener; }    }    public interface SerialPortDataListener { void onDataRead(byte[] data);    }}

可通过DATA_BUFFER_SIZE配置最大支持的缓冲字节长度。通过setSerialPortDataListener设置监听串口读到的数据。
完成以上步骤后就可以通过SerialPortManager读写串口了。

系统应用操作串口的方式

系统应用可以访问到framework隐藏的API,可通过串口相关的API进行串口操作。主要有android.hardware.SerialManager和android.hardware.SerialPort。

SerialManager serialManager = (SerialManager)context.getSystemService(Context.SERIAL_SERVICE);SerialPort serialPort = serialManager.openSerialPort(name, speed);

操作方式而上述三方应用的方式类似。
相关源码地址:
/frameworks/base/core/java/android/hardware/SerialPort.java
/frameworks/base/core/jni/android_hardware_SerialPort.cpp
/frameworks/base/core/java/android/hardware/SerialManager.java
/frameworks/base/services/core/java/com/android/server/SerialService.java
/frameworks/base/services/core/jni/com_android_server_SerialService.cpp
/libnativehelper/JNIHelp.cpp

串口设备

1.串口分为Tx和Rx,Tx代表发送,Rx代表接收。相对客户端所处的设备连接的另一个串口设备而言,客户端的Tx对应远端设备的Rx,Rx对应远端设备的Tx,这样两边设备形成收发回路来进行通信。
2.串口设备间连接需要考虑共地的问题,否则会造成一端接收异常等现象。
3.不同硬件设备所支持的串口读写字节的最大长度不一样,需要考虑是否出现断帧的现象。
4.串口设备节点一般以/dev/tty***格式,例如/dev/ttyMT2、/devttyS0。
5.串口波特率BandRate需要两端设备相匹配,否则会出现乱码,所支持的波特率见上述getBandRate方法。