实现效果

SerialPort.c
将 SerialPort.c 放入 src/main/cpp 目录中。
#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)
/*
* Class: android_serialport_SerialPort
* Method: open
* Signature: (Ljava/lang/String;II)Ljava/io/FileDescriptor;
*/
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: android_serialport_SerialPort
* Method: open
* Signature: (Ljava/lang/String;II)Ljava/io/FileDescriptor;
*/
JNIEXPORT jobject JNICALL Java_com_test_serialtool_SerialPort_open
(JNIEnv *env, jclass thiz, jstring path, jint baudrate, jint flags)
{
int fd;
speed_t speed;
jobject mFileDescriptor;
/* Check arguments */
{
speed = getBaudrate(baudrate);
if (speed == -1) {
/* TODO: throw an exception */
LOGE("Invalid baudrate");
return NULL;
}
}
/* Opening device */
{
jboolean iscopy;
const char *path_utf = (*env)->GetStringUTFChars(env, path, &iscopy);
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(env, path, path_utf);
if (fd == -1)
{
/* Throw an exception */
LOGE("Cannot open port");
/* TODO: throw an exception */
return NULL;
}
}
/* Configure device */
{
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);
cfsetospeed(&cfg, speed);
if (tcsetattr(fd, TCSANOW, &cfg))
{
LOGE("tcsetattr() failed");
close(fd);
/* TODO: throw an exception */
return NULL;
}
}
/* Create a corresponding file descriptor */
{
jclass cFileDescriptor = (*env)->FindClass(env, "java/io/FileDescriptor");
jmethodID iFileDescriptor = (*env)->GetMethodID(env, cFileDescriptor, "<init>", "()V");
jfieldID descriptorID = (*env)->GetFieldID(env, cFileDescriptor, "descriptor", "I");
mFileDescriptor = (*env)->NewObject(env, cFileDescriptor, iFileDescriptor);
(*env)->SetIntField(env, mFileDescriptor, descriptorID, (jint)fd);
}
return mFileDescriptor;
}
/*
* Class: cedric_serial_SerialPort
* Method: close
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_test_serialtool_SerialPort_close
(JNIEnv *env, jobject thiz)
{
jclass SerialPortClass = (*env)->GetObjectClass(env, thiz);
jclass FileDescriptorClass = (*env)->FindClass(env, "java/io/FileDescriptor");
jfieldID mFdID = (*env)->GetFieldID(env, SerialPortClass, "mFd", "Ljava/io/FileDescriptor;");
jfieldID descriptorID = (*env)->GetFieldID(env, FileDescriptorClass, "descriptor", "I");
jobject mFd = (*env)->GetObjectField(env, thiz, mFdID);
jint descriptor = (*env)->GetIntField(env, mFd, descriptorID);
LOGD("close(fd = %d)", descriptor);
close(descriptor);
}
CMakeLists.txt
CmakeLists.txt 与 SerialPort.c 在同一目录下。
cmake_minimum_required(VERSION 3.4.1)
add_library(
serial_port
SHARED
SerialPort.c
)
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 )
target_link_libraries(
serial_port
${log-lib}
)
build.gradle
在 android {} 中添加:
// 支持ndk,加载cmake文件
externalNativeBuild {
cmake {
path file('src/main/cpp/CMakeLists.txt')
}
}
SerialPort.java
SerialPort.java 的包名需与 SerialPort.c 中声明的一致。
package com.test.serialtool;
import android.util.Log;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
/**
* 该类的包名需与生成so时设置的包名相同
*/
public class SerialPort {
static {
System.loadLibrary("serial_port");
}
public native void close();
public native FileDescriptor open(String var1, int var2, int var3);
private File mDevice = null;
private int mBaudrate;
private FileDescriptor mFd = null;
private boolean isConnected = false;
private FileInputStream mFileInputStream = null;
private FileOutputStream mFileOutputStream = null;
public FileInputStream getInputStream() {
return this.mFileInputStream;
}
public FileOutputStream getOutputStream() {
return this.mFileOutputStream;
}
public SerialPort(File device, int baudrate) throws SecurityException, IOException {
if (device == null) {
Log.e("SerialPort", "device not null");
throw new IOException();
} else {
if (!device.canRead() || !device.canWrite()) {
try {
Process 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();
}
this.mDevice = device;
this.mBaudrate = baudrate;
} catch (Exception var5) {
var5.printStackTrace();
throw new SecurityException();
}
}
}
}
public boolean openPort(int flags) throws IOException {
if (this.isConnected) {
return this.isConnected;
} else if (this.mDevice != null) {
this.mFd = this.open(this.mDevice.getAbsolutePath(), this.mBaudrate, flags);
if (this.mFd == null) {
Log.e("SerialPort", "native open returns null");
throw new IOException();
} else {
this.isConnected = true;
this.mFileInputStream = new FileInputStream(this.mFd);
this.mFileOutputStream = new FileOutputStream(this.mFd);
return true;
}
} else {
return false;
}
}
public SerialPort(File device, int baudrate, int flags) throws SecurityException, IOException {
if (device == null) {
Log.e("SerialPort", "device not null");
throw new IOException();
} else {
this.mDevice = device;
this.mBaudrate = baudrate;
if (!device.canRead() || !device.canWrite()) {
try {
Process 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 var6) {
var6.printStackTrace();
throw new SecurityException();
}
}
this.mFd = this.open(device.getAbsolutePath(), baudrate, flags);
if (this.mFd == null) {
Log.e("SerialPort", "native open returns null");
throw new IOException();
} else {
this.isConnected = true;
this.mFileInputStream = new FileInputStream(this.mFd);
this.mFileOutputStream = new FileOutputStream(this.mFd);
}
}
}
public void write(byte[] buffer, int offset, int count) throws IOException {
if (this.isConnected && this.mFileOutputStream != null) {
this.mFileOutputStream.write(buffer, offset, count);
}
}
public boolean isConnected() {
return this.isConnected;
}
public int read(byte[] buffer, int offset, int count) throws IOException {
return this.isConnected && this.mFileInputStream != null ? this.mFileInputStream.read(buffer, offset, count) : 0;
}
public void writeLine(String text) throws IOException, IllegalAccessException {
this.write(text + "\r\n");
}
public void write(String text) throws IOException, IllegalAccessException {
if (this.isConnected && this.mFileOutputStream != null) {
this.mFileOutputStream.write(text.getBytes());
}
}
public int getAvailableByte() throws IOException {
return this.isConnected && this.mFileInputStream != null ? this.mFileInputStream.available() : 0;
}
public String readExisting() throws IOException, IllegalAccessException {
if (this.isConnected && this.mFileInputStream != null) {
int count = this.mFileInputStream.available();
char[] buffer = new char[count];
this.read((char[])buffer, 0, count);
return new String(buffer, 0, count);
} else {
return null;
}
}
public int read(char[] buffer, int offset, int count) throws IOException, IllegalAccessException {
if (this.isConnected && this.mFileInputStream != null) {
if (offset >= 0 && count >= 0 && offset < buffer.length && offset + count <= buffer.length) {
BufferedReader in = new BufferedReader(new InputStreamReader(this.mFileInputStream));
return in.read(buffer, offset, count);
} else {
throw new IndexOutOfBoundsException();
}
} else {
return 0;
}
}
}
SerialPortFinder.java
SerialPortFinder.java 用来查找设备支持的所有串口。
import android.util.Log;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.LineNumberReader;
import java.util.Iterator;
import java.util.Vector;
/**
* 获取设备支持的所有串口
*/
public class SerialPortFinder {
public class Driver {
public Driver(String name, String root) {
mDriverName = name;
mDeviceRoot = root;
}
private String mDriverName;
private String mDeviceRoot;
Vector<File> mDevices = null;
public Vector<File> getDevices() {
if (mDevices == null) {
mDevices = new Vector<File>();
File dev = new File("/dev");
File[] files = dev.listFiles();
if (files != null) {
int i;
for (i=0; i<files.length; i++) {
if (files[i].getAbsolutePath().startsWith(mDeviceRoot)) {
Log.d(TAG, "Found new device: " + files[i]);
mDevices.add(files[i]);
}
}
}
}
return mDevices;
}
public String getName() {
return mDriverName;
}
}
private static final String TAG = "SerialPort";
private Vector<Driver> mDrivers = null;
Vector<Driver> getDrivers() throws IOException {
if (mDrivers == null) {
mDrivers = new Vector<Driver>();
LineNumberReader r = new LineNumberReader(new FileReader("/proc/tty/drivers"));
String l;
while((l = r.readLine()) != null) {
// Issue 3:
// Since driver name may contain spaces, we do not extract driver name with split()
String drivername = l.substring(0, 0x15).trim();
String[] w = l.split(" +");
if ((w.length >= 5) && (w[w.length-1].equals("serial"))) {
Log.d(TAG, "Found new driver " + drivername + " on " + w[w.length-4]);
mDrivers.add(new Driver(drivername, w[w.length-4]));
}
}
r.close();
}
return mDrivers;
}
public String[] getAllDevices() {
Vector<String> devices = new Vector<String>();
// Parse each driver
Iterator<Driver> itdriv;
try {
itdriv = getDrivers().iterator();
while(itdriv.hasNext()) {
Driver driver = itdriv.next();
Iterator<File> itdev = driver.getDevices().iterator();
while(itdev.hasNext()) {
String device = itdev.next().getName();
String value = String.format("%s (%s)", device, driver.getName());
devices.add(value);
}
}
} catch (IOException e) {
e.printStackTrace();
}
return devices.toArray(new String[devices.size()]);
}
public String[] getAllDevicesPath() {
Vector<String> devices = new Vector<String>();
// Parse each driver
Iterator<Driver> itdriv;
try {
itdriv = getDrivers().iterator();
while(itdriv.hasNext()) {
Driver driver = itdriv.next();
Iterator<File> itdev = driver.getDevices().iterator();
while(itdev.hasNext()) {
String device = itdev.next().getAbsolutePath();
devices.add(device);
}
}
} catch (IOException e) {
e.printStackTrace();
}
return devices.toArray(new String[devices.size()]);
}
}
activity_serial_tool.xml
布局文件。
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<!--打开/关闭串口的点击事件-->
<variable
name="onOpenClickListener"
type="android.view.View.OnClickListener" />
<!--刷新的开关事件-->
<variable
name="onRefreshCheckedChangeListener"
type="android.widget.CompoundButton.OnCheckedChangeListener" />
<!--清屏的开关事件-->
<variable
name="onClearCheckedChangeListener"
type="android.widget.CompoundButton.OnCheckedChangeListener" />
<!--保存的开关事件-->
<variable
name="onSaveCheckedChangeListener"
type="android.widget.CompoundButton.OnCheckedChangeListener" />
<!--选择常用命令的点击事件-->
<variable
name="onSelectCommandClickListener"
type="android.view.View.OnClickListener" />
<!--发送的点击事件-->
<variable
name="onSendClickListener"
type="android.view.View.OnClickListener" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="串口号:"/>
<Spinner
android:id="@+id/spinner_serial_port"
android:layout_width="match_parent"
android:layout_height="match_parent"></Spinner>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center"
android:layout_marginTop="10dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="波特率:" />
<Spinner
android:id="@+id/spinner_baud_rate"
android:layout_width="match_parent"
android:layout_height="match_parent"></Spinner>
</LinearLayout>
</LinearLayout>
<Button
android:id="@+id/btn_open"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="打开"
android:onClick="@{onOpenClickListener}"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="10dp"
android:orientation="vertical">
<TextView
android:id="@+id/tv_console"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="#e3e3e3"
android:fadeScrollbars="false"
android:scrollbars="vertical"
android:gravity="bottom" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<CheckBox
android:id="@+id/cb_refresh"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:checked="true"
android:text="刷新"
android:onCheckedChanged="@{onRefreshCheckedChangeListener}"/>
<CheckBox
android:id="@+id/cb_clear"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="清屏"
android:onCheckedChanged="@{onClearCheckedChangeListener}"/>
<CheckBox
android:id="@+id/cb_hex"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Hex"/>
<CheckBox
android:id="@+id/cb_save"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="保存"
android:onCheckedChanged="@{onSaveCheckedChangeListener}"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="5dp">
<EditText
android:id="@+id/et_command"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_alignParentBottom="true" />
<ImageView
android:id="@+id/iv_select_command"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:src="@drawable/ic_down"
android:onClick="@{onSelectCommandClickListener}"/>
<Button
android:id="@+id/btn_send"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="发送"
android:onClick="@{onSendClickListener}"/>
</LinearLayout>
</LinearLayout>
</LinearLayout>
</layout>
SerialToolActivity.java
public class SerialToolActivity extends BaseActivity<AndroidViewModel, ActivitySerialToolBinding> {
@Override
protected int getContentViewId() {
return R.layout.activity_serial_tool;
}
private static String PREF_KEY_LAST_PATH = "serial_path";
private static String PREF_KEY_LAST_BAUD_RATE = "serial_baud_rate";
/**
* ascii格式的常用命令
*/
private File commandFile = null;
/**
* 十六进制的常用命令
*/
private File hexCommandFile = null;
private SerialPort port;
private boolean isOpen;
private Thread readThread;
private Handler handler = new MyHandler();
private File saveFile = null;
/**
* 常用命令
*/
private CommandSet commands = null;
/**
* 常用十六进制字符串命令
*/
private CommandSet hexCommands = null;
/**
* websocket重连线程
*/
private ExecutorService reconnectService = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setTitle("串口工具");
// 初始化软件
new RxPermissions(this)
.request(Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE)
.subscribe(aBoolean -> {
if (aBoolean) {
init();
} else {
finish();
}
});
// 初始化界面
initView();
// 初始化事件
initListener();
}
/**
* 初始化
*/
private void init() {
try {
commandFile = new File(DirectoryManager.MAIN.getPath(), "command.txt");
hexCommandFile = new File(DirectoryManager.MAIN.getPath(), "hexCommand.txt");
// 拷贝assets中的文件
String[] files = getAssets().list("");
for (String file : files) {
if (file.equals(commandFile.getName())) {
DirectoryManager.copyAssets(this, file, DirectoryManager.MAIN.getAbsolutePath(), false);
} else if (file.equals(hexCommandFile.getName())) {
DirectoryManager.copyAssets(this, file, DirectoryManager.MAIN.getAbsolutePath(), false);
}
}
FileUtil.notifySystemToScan(DirectoryManager.MAIN.getPath());
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 初始化界面
*/
private void initView() {
// 获取串口号
String[] devices = new SerialPortFinder().getAllDevicesPath();
ArrayAdapter<String> deviceAdapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_dropdown_item, devices);
dataBinding.spinnerSerialPort.setAdapter(deviceAdapter);
int index = Arrays.asList(devices).indexOf(prefs.getString(PREF_KEY_LAST_PATH, ""));
if (index > -1) {
dataBinding.spinnerSerialPort.setSelection(index);
}
// 获取波特率
String[] baudrates = getResources().getStringArray(R.array.serial_baud_rates);
ArrayAdapter<String> baudrateAdapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_dropdown_item, baudrates);
dataBinding.spinnerBaudRate.setAdapter(baudrateAdapter);
index = Arrays.asList(baudrates).indexOf(prefs.getString(PREF_KEY_LAST_BAUD_RATE, ""));
if (index > -1) {
dataBinding.spinnerBaudRate.setSelection(index);
}
dataBinding.tvConsole.setMovementMethod(ScrollingMovementMethod.getInstance());
}
private void initListener() {
// 打开或关闭串口
dataBinding.setOnOpenClickListener(v -> {
if (isOpen == false) {
open();
}
else {
close();
dataBinding.btnOpen.setText("打开");
dataBinding.spinnerSerialPort.setEnabled(true);
dataBinding.spinnerBaudRate.setEnabled(true);
Toast.makeText(this, "串口已关闭!", Toast.LENGTH_SHORT).show();
}
});
// 发送命令
dataBinding.setOnSendClickListener(v -> {
if (isOpen && port != null) {
String command = dataBinding.etCommand.getText().toString().trim();
if (TextUtils.isEmpty(command) == false) {
if (dataBinding.cbHex.isChecked() == false) {
try {
// 发送ASCII格式的命令
port.writeLine(command);
Toast.makeText(this, "发送成功!", Toast.LENGTH_SHORT).show();
} catch (Exception e) {
Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show();
}
} else {
try {
// 发送Hex格式的命令
// 将十六进制字符串转为字节数组
byte[] data = Helper.hexStr2BytesWithBlank(command);
if (data != null) {
port.write(data, 0, data.length);
Toast.makeText(this, "发送成功!", Toast.LENGTH_SHORT).show();
}
} catch (Exception e) {
Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show();
}
}
}
}
});
// 清屏
dataBinding.setOnClearCheckedChangeListener((buttonView, isChecked) -> {
if (isChecked) {
dataBinding.tvConsole.setText("");
dataBinding.cbClear.setChecked(false);
}
});
// 勾选保存,创建新文件进行保存
dataBinding.setOnSaveCheckedChangeListener((buttonView, isChecked) -> {
// 每次勾选创建新的文件
if (isChecked) {
saveFile = new File(DirectoryManager.MAIN.getPath(), String.format("serial_%s.txt", Helper.getShortDate()));
Toast.makeText(this, String.format("数据保存到:%s", saveFile.getAbsolutePath()), Toast.LENGTH_LONG).show();
}
});
// 选择常用命令
dataBinding.setOnSelectCommandClickListener(v -> {
if (dataBinding.cbHex.isChecked()) {
// 加载十六进制常用命令
if (hexCommands == null) {
hexCommands = getCommandSet(hexCommandFile);
}
if (hexCommands == null) {
return;
}
List<String> comments = hexCommands.getComments();
if (comments != null && comments.size() > 0) {
DialogUtil.showSingleSelectDialog(this, comments, (dialog, which) -> {
dialog.dismiss();
dataBinding.etCommand.setText(hexCommands.getCommand(which));
});
}
} else {
// 加载ASCII常用命令
if (commands == null) {
commands = getCommandSet(commandFile);
}
if (commands == null) {
return;
}
List<String> comments = commands.getComments();
if (comments != null && comments.size() > 0) {
DialogUtil.showSingleSelectDialog(this, comments, (dialog, which) -> {
dialog.dismiss();
dataBinding.etCommand.setText(commands.getCommand(which));
});
}
}
});
}
@Override
protected void onDestroy() {
close();
super.onDestroy();
}
/**
* 点击打开按钮
*/
private void open() {
dataBinding.tvConsole.setText("");
String devicePort = dataBinding.spinnerSerialPort.getSelectedItem().toString();
int baudrate = Integer.parseInt(dataBinding.spinnerBaudRate.getSelectedItem().toString());
if (open(devicePort, baudrate)) {
dataBinding.btnOpen.setText("关闭");
dataBinding.spinnerSerialPort.setEnabled(false);
dataBinding.spinnerBaudRate.setEnabled(false);
Toast.makeText(this, "打开串口成功!", Toast.LENGTH_SHORT).show();
readThread = new Thread(new ReadRunnable());
readThread.start();
prefs.edit().putString(PREF_KEY_LAST_PATH, devicePort)
.putString(PREF_KEY_LAST_BAUD_RATE, String.valueOf(baudrate)).commit();
} else {
Toast.makeText(this, "打开串口失败!", Toast.LENGTH_SHORT).show();
}
}
/**
* 打开串口
*/
private boolean open(String devicePort, int baudrate) {
if (port != null) {
close();
}
try {
port = new SerialPort(new File(devicePort), baudrate, 0);
if (port != null) {
isOpen = port.isConnected();
}
} catch (Exception e) {
return false;
}
return isOpen;
}
/**
* 关闭串口
*/
private void close() {
isOpen = false;
if (port != null) {
if (port.isConnected()) {
port.close();
}
port = null;
}
}
/**
* 避免数据过多导致TextView无法刷新
*/
private void appendMsg(String msg) {
if (dataBinding.cbRefresh.isChecked() == false) {
return;
}
int len = dataBinding.tvConsole.getText().length();
if (len >= 3072) {
int removeLength = 3072 / 3 * 2;
CharSequence oldMsg = dataBinding.tvConsole.getText().subSequence(len - removeLength, len);
dataBinding.tvConsole.setText(oldMsg);
}
msg = "\r\n" + msg;
dataBinding.tvConsole.append(msg);
}
/**
* 保存到文件
*/
private void saveMsg(String msg) {
if (dataBinding.cbSave.isChecked() == false) {
return;
}
try {
FileUtil.writeFile(saveFile, msg, true, true);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 读取文件,获取命令集
*/
public CommandSet getCommandSet(File commandFile) {
CommandSet commands = new CommandSet();
commands.load(commandFile);
return commands;
}
/**
* 显示接收的数据
*/
private class MyHandler extends Handler {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 1:
// 显示到界面中
appendMsg(msg.obj.toString());
// 保存到文件
saveMsg(msg.obj.toString());
break;
}
}
}
/**
* 接收数据
*/
private class ReadRunnable implements Runnable {
@Override
public void run() {
while (isOpen) {
try {
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
byte[] temp = new byte[4095];
try {
if (port == null) {
break;
}
int len = port.read(temp, 0, temp.length);
if (len > 0) {
String msg = null;
if (dataBinding.cbHex.isChecked()) {
msg = Helper.byte2HexStr(temp, 0, len);
} else {
msg = new String(temp, 0, len);
}
msg.trim();
handler.obtainMessage(1, msg).sendToTarget();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
源码下载
https://download.csdn.net/download/doris_d/13121730
这篇博客介绍了如何利用Android NDK实现一个串口工具,包括在SerialPort.c中处理串口通信,配置CMakeLists.txt和build.gradle文件,创建SerialPort.java和SerialPortFinder.java来管理和查找串口,以及设计SerialToolActivity.java和activity_serial_tool.xml布局。提供了源码下载链接和相关参考资料。
2万+

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



