
由于三方应用无法通过系统的串口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#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; { speed = getBandRate(baudrate); if (speed == -1) { return -1; } } { 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) { LOGE("Cannot open port"); return -1; } } { 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; } } { 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);
}
}
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);
}
}
private boolean checkAccessPermission(String name) {
File device = new File(name);
if (!device.canRead() || !device.canWrite()) {
try {
Process su;
su = Runtime.getRuntime().exec("/system/bin/su");
String cmd = "chmod 666 " + device.getAbsolutePath() + "n"
+ "exitn";
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方法。