java使用Rxtx实现串口通信调试工具

本文实例为大家分享了java使用Rxtx实现串口通信调试工具的具体代码,供大家参考,具体内容如下

最终效果如下图:

1、把rxtxParallel.dll、rxtxSerial.dll拷贝到:C:\WINDOWS\system32下。
2、RXTXcomm.jar 添加到项目类库中。

代码:

package serialPort;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.TooManyListenersException;

import gnu.io.CommPort;
import gnu.io.CommPortIdentifier;
import gnu.io.NoSuchPortException;
import gnu.io.PortInUseException;
import gnu.io.SerialPort;
import gnu.io.SerialPortEventListener;
import gnu.io.UnsupportedCommOperationException;

/**串口服务类,提供打开、关闭串口,读取、发送串口数据等服务
 */
public class SerialTool {

 private static SerialTool serialTool = null;

 static {
  //在该类被ClassLoader加载时就初始化一个SerialTool对象
  if (serialTool == null) {
   serialTool = new SerialTool();
  }
 }

 //私有化SerialTool类的构造方法,不允许其他类生成SerialTool对象
 private SerialTool() {}
 /**
  * 获取提供服务的SerialTool对象
  * @return serialTool
  */
 public static SerialTool getSerialTool() {

  if (serialTool == null) {
   serialTool = new SerialTool();
  }
  return serialTool;
 }
 /**
  * 查找所有可用端口
  * @return 可用端口名称列表
  */
 public static final List<String> findPort() {

  //获得当前所有可用串口
  @SuppressWarnings("unchecked")
  Enumeration<CommPortIdentifier> portList = CommPortIdentifier.getPortIdentifiers();
  List<String> portNameList = new ArrayList<>();
  //将可用串口名添加到List并返回该List
  while (portList.hasMoreElements()) {
   String portName = portList.nextElement().getName();
   portNameList.add(portName);
  }
  return portNameList;
 }
 /**
  * 打开串口
  * @param portName 端口名称
  * @param baudrate 波特率
  * @return 串口对象
  * @throws UnsupportedCommOperationException
  * @throws PortInUseException
  * @throws NoSuchPortException
  */
 public static final SerialPort openPort(String portName, int baudrate) throws UnsupportedCommOperationException, PortInUseException, NoSuchPortException {

  //通过端口名识别端口
  CommPortIdentifier portIdentifier = CommPortIdentifier.getPortIdentifier(portName);
  //打开端口,并给端口名字和一个timeout(打开操作的超时时间)
  CommPort commPort = portIdentifier.open(portName, 2000);
  //判断是不是串口
  if (commPort instanceof SerialPort) {
   SerialPort serialPort = (SerialPort) commPort;
   //设置一下串口的波特率等参数
   serialPort.setSerialPortParams(baudrate, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
   return serialPort;
  }
  return null;
 }
 /**
  * 关闭串口
  * @param serialport 待关闭的串口对象
  */
 public static void closePort(SerialPort serialPort) {

  if (serialPort != null) {
   serialPort.close();
   serialPort = null;
  }
 }
 /**
  * 往串口发送数据
  * @param serialPort 串口对象
  * @param order 待发送数据
  * @throws IOException
  */
 public static void sendToPort(SerialPort serialPort, byte[] order) throws IOException {

  OutputStream out = null;
  out = serialPort.getOutputStream();
  out.write(order);
  out.flush();
  out.close();
 }
 /**
  * 从串口读取数据
  * @param serialPort 当前已建立连接的SerialPort对象
  * @return 读取到的数据
  * @throws IOException
  */
 public static byte[] readFromPort(SerialPort serialPort) throws IOException {

  InputStream in = null;
  byte[] bytes = null;
  try {
   in = serialPort.getInputStream();
   int bufflenth = in.available(); //获取buffer里的数据长度
   while (bufflenth != 0) {
    bytes = new byte[bufflenth]; //初始化byte数组为buffer中数据的长度
    in.read(bytes);
    bufflenth = in.available();
   }
  } catch (IOException e) {
   throw e;
  } finally {
   if (in != null) {
    in.close();
    in = null;
   }
  }
  return bytes;
 }
 /**添加监听器
  * @param port  串口对象
  * @param listener 串口监听器
  * @throws TooManyListenersException
  */
 public static void addListener(SerialPort port, SerialPortEventListener listener) throws TooManyListenersException {

  //给串口添加监听器
  port.addEventListener(listener);
  //设置当有数据到达时唤醒监听接收线程
  port.notifyOnDataAvailable(true);
  //设置当通信中断时唤醒中断线程
  port.notifyOnBreakInterrupt(true);
 }
}
package serialPort;

import java.awt.Color;
import java.awt.Font;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.IOException;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.TooManyListenersException;

import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.border.TitledBorder;

import gnu.io.NoSuchPortException;
import gnu.io.PortInUseException;
import gnu.io.SerialPort;
import gnu.io.SerialPortEvent;
import gnu.io.SerialPortEventListener;
import gnu.io.UnsupportedCommOperationException;

/**
 * 监测数据显示类
 * @author Zhong
 *
 */
public class SerialView extends JFrame {

 /**
  */
 private static final long serialVersionUID = 1L;

 //设置window的icon
 Toolkit toolKit = getToolkit();
 Image icon = toolKit.getImage(SerialView.class.getResource("computer.png"));
 DateTimeFormatter df= DateTimeFormatter.ofPattern("YYYY-MM-dd HH:mm:ss.SSS");

 private JComboBox<String> commChoice;
 private JComboBox<String> bpsChoice;
 private JButton openSerialButton;
 private JButton sendButton;
 private JTextArea sendArea;
 private JTextArea receiveArea;
 private JButton closeSerialButton;

 private List<String> commList = null; //保存可用端口号
 private SerialPort serialPort = null; //保存串口对象

 /**类的构造方法
  * @param client
  */
 public SerialView() {

  init();
  TimerTask task = new TimerTask() {
   @Override
   public void run() {
    commList = SerialTool.findPort(); //程序初始化时就扫描一次有效串口
    //检查是否有可用串口,有则加入选项中
    if (commList == null || commList.size()<1) {
     JOptionPane.showMessageDialog(null, "没有搜索到有效串口!", "错误", JOptionPane.INFORMATION_MESSAGE);
    }else{
     commChoice.removeAllItems();
     for (String s : commList) {
      commChoice.addItem(s);
     }
    }
   }
  };
  Timer timer = new Timer();
  timer.scheduleAtFixedRate(task, 0, 10000);
  listen();

 }
 /**
  */
 private void listen(){

  //打开串口连接
  openSerialButton.addActionListener(new ActionListener() {

   public void actionPerformed(ActionEvent e) {
    //获取串口名称
    String commName = (String) commChoice.getSelectedItem();
    //获取波特率
    String bpsStr = (String) bpsChoice.getSelectedItem();
    //检查串口名称是否获取正确
    if (commName == null || commName.equals("")) {
     JOptionPane.showMessageDialog(null, "没有搜索到有效串口!", "错误", JOptionPane.INFORMATION_MESSAGE);
    }else {
     //检查波特率是否获取正确
     if (bpsStr == null || bpsStr.equals("")) {
      JOptionPane.showMessageDialog(null, "波特率获取错误!", "错误", JOptionPane.INFORMATION_MESSAGE);
     }else {
      //串口名、波特率均获取正确时
      int bps = Integer.parseInt(bpsStr);
      try {
       //获取指定端口名及波特率的串口对象
       serialPort = SerialTool.openPort(commName, bps);
       SerialTool.addListener(serialPort, new SerialListener());
       if(serialPort==null) return;
       //在该串口对象上添加监听器
       closeSerialButton.setEnabled(true);
       sendButton.setEnabled(true);
       openSerialButton.setEnabled(false);
       String time=df.format(LocalDateTime.ofInstant(Instant.ofEpochMilli(System.currentTimeMillis()),ZoneId.of("Asia/Shanghai")));
       receiveArea.append(time+" ["+serialPort.getName().split("/")[3]+"] : "+" 连接成功..."+"\r\n");
       receiveArea.setCaretPosition(receiveArea.getText().length());
      } catch (UnsupportedCommOperationException | PortInUseException | NoSuchPortException | TooManyListenersException e1) {
       e1.printStackTrace();
      }
     }
    }
   }
  });
  //发送数据
  sendButton.addMouseListener(new MouseAdapter() {
   @Override
   public void mouseClicked(MouseEvent e) {
    if(!sendButton.isEnabled())return;
    String message= sendArea.getText();
    //"FE0400030001D5C5"
    try {
     SerialTool.sendToPort(serialPort, hex2byte(message));
    } catch (IOException e1) {
     e1.printStackTrace();
    }
   }
  });
  //关闭串口连接
  closeSerialButton.addMouseListener(new MouseAdapter() {
   @Override
   public void mouseClicked(MouseEvent e) {
    if(!closeSerialButton.isEnabled())return;
    SerialTool.closePort(serialPort);
    String time=df.format(LocalDateTime.ofInstant(Instant.ofEpochMilli(System.currentTimeMillis()),ZoneId.of("Asia/Shanghai")));
    receiveArea.append(time+" ["+serialPort.getName().split("/")[3]+"] : "+" 断开连接"+"\r\n");
    receiveArea.setCaretPosition(receiveArea.getText().length());
    openSerialButton.setEnabled(true);
    closeSerialButton.setEnabled(false);
    sendButton.setEnabled(false);
   }
  });
 }
 /**
  * 主菜单窗口显示;
  * 添加JLabel、按钮、下拉条及相关事件监听;
  */
 private void init() {

  this.setBounds(WellcomView.LOC_X, WellcomView.LOC_Y, WellcomView.WIDTH, WellcomView.HEIGHT);
  this.setTitle("串口调试");
  this.setIconImage(icon);
  this.setBackground(Color.gray);
  this.setLayout(null);

  Font font =new Font("微软雅黑", Font.BOLD, 16);

  receiveArea=new JTextArea(18, 30);
  receiveArea.setEditable(false);
  JScrollPane receiveScroll = new JScrollPane(receiveArea);
  receiveScroll.setBorder(new TitledBorder("接收区"));
  //滚动条自动出现 FE0400030001D5C5
  receiveScroll.setHorizontalScrollBarPolicy(
    JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
  receiveScroll.setVerticalScrollBarPolicy(
    JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
  receiveScroll.setBounds(52, 20, 680,340);
  this.add(receiveScroll);

  JLabel chuankou=new JLabel(" 串口选择: ");
  chuankou.setFont(font);
  chuankou.setBounds(50, 380, 100,50);
  this.add(chuankou);

  JLabel botelv=new JLabel(" 波 特 率: ");
  botelv.setFont(font);
  botelv.setBounds(290, 380, 100,50);
  this.add(botelv);

  //添加串口选择选项
  commChoice = new JComboBox<String>(); //串口选择(下拉框)
  commChoice.setBounds(145, 390, 100, 30);
  this.add(commChoice);

  //添加波特率选项
  bpsChoice = new JComboBox<String>(); //波特率选择
  bpsChoice.setBounds(380, 390, 100, 30);
  bpsChoice.addItem("1500");
  bpsChoice.addItem("2400");
  bpsChoice.addItem("4800");
  bpsChoice.addItem("9600");
  bpsChoice.addItem("14400");
  bpsChoice.addItem("19500");
  bpsChoice.addItem("115500");
  this.add(bpsChoice);

  //添加打开串口按钮
  openSerialButton = new JButton("连接");
  openSerialButton.setBounds(540, 390, 80, 30);
  openSerialButton.setFont(font);
  openSerialButton.setForeground(Color.darkGray);
  this.add(openSerialButton);

  //添加关闭串口按钮
  closeSerialButton = new JButton("关闭");
  closeSerialButton.setEnabled(false);
  closeSerialButton.setBounds(650, 390, 80, 30);
  closeSerialButton.setFont(font);
  closeSerialButton.setForeground(Color.darkGray);
  this.add(closeSerialButton);

  sendArea=new JTextArea(30,20);
  JScrollPane sendScroll = new JScrollPane(sendArea);
  sendScroll.setBorder(new TitledBorder("发送区"));
  //滚动条自动出现
  sendScroll.setHorizontalScrollBarPolicy(
    JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
  sendScroll.setVerticalScrollBarPolicy(
    JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
  sendScroll.setBounds(52, 450, 500,100);
  this.add(sendScroll);

  sendButton = new JButton("发 送");
  sendButton.setBounds(610, 520, 120, 30);
  sendButton.setFont(font);
  sendButton.setForeground(Color.darkGray);
  sendButton.setEnabled(false);
  this.add(sendButton);

  this.setResizable(false); //窗口大小不可更改
  this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  this.setVisible(true);
 }

 /**字符串转16进制
  * @param hex
  * @return
  */
 private byte[] hex2byte(String hex) {

  String digital = "0123456789ABCDEF";
  String hex1 = hex.replace(" ", "");
  char[] hex2char = hex1.toCharArray();
  byte[] bytes = new byte[hex1.length() / 2];
  byte temp;
  for (int p = 0; p < bytes.length; p++) {
   temp = (byte) (digital.indexOf(hex2char[2 * p]) * 16);
   temp += digital.indexOf(hex2char[2 * p + 1]);
   bytes[p] = (byte) (temp & 0xff);
  }
  return bytes;
 }
 /**字节数组转16进制
  * @param b
  * @return
  */
 private String printHexString(byte[] b) {

  StringBuffer sbf=new StringBuffer();
  for (int i = 0; i < b.length; i++) {
   String hex = Integer.toHexString(b[i] & 0xFF);
   if (hex.length() == 1) {
    hex = '0' + hex;
   }
   sbf.append(hex.toUpperCase()+" ");
  }
  return sbf.toString().trim();
 }
 /**
  * 以内部类形式创建一个串口监听类
  * @author zhong
  */
 class SerialListener implements SerialPortEventListener {

  /**
   * 处理监控到的串口事件
   */
  public void serialEvent(SerialPortEvent serialPortEvent) {

   switch (serialPortEvent.getEventType()) {
   case SerialPortEvent.BI: // 10 通讯中断
    JOptionPane.showMessageDialog(null, "与串口设备通讯中断", "错误", JOptionPane.INFORMATION_MESSAGE);
    break;
   case SerialPortEvent.OE: // 7 溢位(溢出)错误
    break;
   case SerialPortEvent.FE: // 9 帧错误
    break;
   case SerialPortEvent.PE: // 8 奇偶校验错误
    break;
   case SerialPortEvent.CD: // 6 载波检测
    break;
   case SerialPortEvent.CTS: // 3 清除待发送数据
    break;
   case SerialPortEvent.DSR: // 4 待发送数据准备好了
    break;
   case SerialPortEvent.RI: // 5 振铃指示
    break;
   case SerialPortEvent.OUTPUT_BUFFER_EMPTY: // 2 输出缓冲区已清空
    break;
   case SerialPortEvent.DATA_AVAILABLE: // 1 串口存在可用数据
    String time=df.format(LocalDateTime.ofInstant(Instant.ofEpochMilli(System.currentTimeMillis()),ZoneId.of("Asia/Shanghai")));
    byte[] data;//FE0400030001D5C5
    try {
     data = SerialTool.readFromPort(serialPort);
     receiveArea.append(time+" ["+serialPort.getName().split("/")[3]+"] : "+ printHexString(data)+"\r\n");
     receiveArea.setCaretPosition(receiveArea.getText().length());
    } catch (IOException e) {
     e.printStackTrace();
    }
    break;
   default:
    break;
   }
  }
 }
}
package serialPort;

import java.awt.Color;
import java.awt.Font;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;

import javax.swing.JFrame;
import javax.swing.JLabel;

/**
 * @author bh
 * 如果运行过程中抛出 java.lang.UnsatisfiedLinkError 错误,
 * 请将rxtx解压包中的 rxtxParallel.dll,rxtxSerial.dll 这两个文件复制到 C:\Windows\System32 目录下即可解决该错误。
 */
public class WellcomView {

 /** 程序界面宽度*/
 public static final int WIDTH = 800;
 /** 程序界面高度*/
 public static final int HEIGHT = 620;
 /** 程序界面出现位置(横坐标) */
 public static final int LOC_X = 200;
 /** 程序界面出现位置(纵坐标)*/
 public static final int LOC_Y = 70;

 private JFrame jFrame;

 /**主方法
  * @param args //
  */
 public static void main(String[] args) {

  new WellcomView();
 }
 public WellcomView() {

  init();
  listen();
 }
 /**
  */
 private void listen() {

  //添加键盘监听器
  jFrame.addKeyListener(new KeyAdapter() {
   public void keyReleased(KeyEvent e) {
    int keyCode = e.getKeyCode();
    if (keyCode == KeyEvent.VK_ENTER) { //当监听到用户敲击键盘enter键后执行下面的操作
     jFrame.setVisible(false); //隐去欢迎界面
     new SerialView(); //主界面类(显示监控数据主面板)
    }
   }
  });
 }
 /**
  * 显示主界面
  */
 private void init() {

  jFrame=new JFrame("串口调试");
  jFrame.setBounds(LOC_X, LOC_Y, WIDTH, HEIGHT); //设定程序在桌面出现的位置
  jFrame.setLayout(null);
  //设置window的icon(这里我自定义了一下Windows窗口的icon图标,因为实在觉得哪个小咖啡图标不好看 = =)
  Toolkit toolKit = jFrame.getToolkit();
  Image icon = toolKit.getImage(WellcomView.class.getResource("computer.png"));
  jFrame.setIconImage(icon);
  jFrame.setBackground(Color.white); //设置背景色

  JLabel huanyin=new JLabel("欢迎使用串口调试工具");
  huanyin.setBounds(170, 80,600,50);
  huanyin.setFont(new Font("微软雅黑", Font.BOLD, 40));
  jFrame.add(huanyin);

  JLabel banben=new JLabel("Version:1.0 Powered By:cyq");
  banben.setBounds(180, 390,500,50);
  banben.setFont(new Font("微软雅黑", Font.ITALIC, 26));
  jFrame.add(banben);

  JLabel enter=new JLabel("————点击Enter键进入主界面————");
  enter.setBounds(100, 480,600,50);
  enter.setFont(new Font("微软雅黑", Font.BOLD, 30));
  jFrame.add(enter);

  jFrame.setResizable(false); //窗口大小不可更改
  jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  jFrame.setVisible(true); //显示窗口
 }
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • 基于Java编写串口通信工具

    最近一门课要求编写一个上位机串口通信工具,我基于Java编写了一个带有图形界面的简单串口通信工具,下面详述一下过程,供大家参考 ^_^ 一: 首先,你需要下载一个额外的支持Java串口通信操作的jar包,由于java.comm比较老了,而且不支持64位系统,这里推荐Rxtx这个jar包(32位/64位均支持). 官方下载地址:http://fizzed.com/oss/rxtx-for-java (注:可能需要FQ才能下载) 不能FQ的童鞋,可以在这里下载: http://xiazai.jb51

  • 使用Java实现串口通信

    1.介绍 使用Java实现的串口通信程序,支持十六进制数据的发送与接收. 源码下载地址:http://download.csdn.net/detail/kong_gu_you_lan/9611343 效果图如下: 2.RXTXcomm Java串口通信依赖的jar包RXTXcomm.jar 下载地址:http://download.csdn.net/detail/kong_gu_you_lan/9611334 内含32位与64位版本 使用方法: 拷贝 RXTXcomm.jar 到 JAVA_HO

  • Java使用开源Rxtx实现串口通讯

    本文实例为大家分享了Java使用开源Rxtx实现串口通讯的具体代码,供大家参考,具体内容如下 使用方法: windows平台: 1.把rxtxParallel.dll.rxtxSerial.dll拷贝到:C:\WINDOWS\system32下. 2.如果是在开发的时候(JDK),需要把RXTXcomm.jar.rxtxParallel.dll.rxtxSerial.dll拷贝到..\jre...\lib\ext下:如:D:\Program Files\Java\jre1.6.0_02\lib\

  • java 串口通信详细及简单实例

    java 实现串口通信 最近做了一个与硬件相关的项目,刚开始听说用java和硬件打交道,着实下了一大跳.java也可以操作硬件? 后来接触到是用java通过串口通信控制硬件感觉使用起来还不错,也很方便. 特拿出来和大家一起分享一下. 准备工作: 首先到SUN官网下载一个zip包:javacomm20-win32.zip 其中重要的有这几个文件: win32com.dll comm.jar javax.comm.properties 按照说明配置好环境,如下: 将win32com.dll复制到<J

  • java 串口通信实现流程示例

    1.下载64位rxtx for java 链接:http://fizzed.com/oss/rxtx-for-java 2.下载下来的包解压后按照说明放到JAVA_HOME即JAVA的安装路径下面去 3.在maven的pom.xml下添加 <dependency> <groupId>org.rxtx</groupId> <artifactId>rxtx</artifactId> <version>2.1.7</version&g

  • Java实现的串口通信功能示例

    本文实例讲述了Java实现的串口通信功能.分享给大家供大家参考,具体如下: 用Java实现串口通信(windows系统下),需要用到sun提供的串口包 javacomm20-win32.zip.其中要用到三个文件,配置如下: 1.comm.jar放置到 JAVA_HOME/jre/lib/ext; 2.win32com.dll放置到 JAVA_HOME/bin; 3.javax.comm.properties 两个地方都要放     jre/lib(也就是在JAVA文件夹下的jre)    JA

  • java使用Rxtx实现串口通信调试工具

    本文实例为大家分享了java使用Rxtx实现串口通信调试工具的具体代码,供大家参考,具体内容如下 最终效果如下图: 1.把rxtxParallel.dll.rxtxSerial.dll拷贝到:C:\WINDOWS\system32下. 2.RXTXcomm.jar 添加到项目类库中. 代码: package serialPort; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream

  • 使用Java实现简单串口通信

    本博文参考自https://www.jb51.net/article/100269.htm www.jb51.net/article/100269.htm 没想到挺多人需要这个的,很高兴这篇文章能对大家有帮助,主要的工具类博文里已经有了,当然,要小工具源码的留言邮箱即可. 2019.09.05 最近接触到了串口及其读写,在此记录java进行串口读写的过程. 1.导入支持java串口通信的jar包: 在maven项目的pom.xml中添加RXTXcomm的依赖 或者 下载RXTXcomm.jar并

  • Android 串口通信编程及串口协议分析

    Android 串口通信编程:嵌入式编程和可穿戴设备及智能设备都会用到串口,这里就带大家分析下, 一,android串口通信 串口通信采用一个第三方开源项目,实现串口数据收发. 1. 使用了http://code.google.com/p/android-serialport-api/的项目的serialport api和jni: 2. 支持4串口同时收发,有定时自动发送功能,收发模式可选Txt或Hex模式: 3.  n,8,1,没得选: 4. 为减轻界面卡顿的情况,接收区的刷新采用单独的线程进

  • Android串口通信之串口读写实例

    在Android串口通信:基本知识梳理的基础上,我结合我项目中使用串口的实例,进行总结: Android使用jni直接进行串口设备的读写网上已经有开源项目了,本文是基于网上的开源项目在实际项目中的使用做的调整和优化: Google串口开源项目 下面是我项目中的相关代码及介绍: 1.SerialPort.cpp /* * Copyright 2009 Cedric Priscal * * Licensed under the Apache License, Version 2.0 (the "Li

随机推荐