Android利用CMake进行JNI串口操作

三方应用的操作串口方式

由于三方应用无法通过系统的串口API操作串口,需要借助JNI访问系统串口节点。
步骤如下:
新建Android Native LIbrary,生成CMakeLists.txt和SerialPort.cpp。
在这里插入图片描述
在这里插入图片描述

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

#include <termios.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <cstring>
#include <jni.h>

#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, "<init>", "()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方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

言并肃

感谢大哥支持!您的鼓励是我动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值