Java Study Notes (Day 9)

Network programming refers to writing programs that communicate with other computers. Java has encapsulated the things required by network programs into different classes. As long as objects of these classes are created and corresponding methods are used, high-quality network communication programs can be written.

The TCP/IP model is a hierarchical structure, which is divided into four layers, namely the application layer, the transport layer, the Internet layer and the host-to-network layer. Each layer implements specific functions, provides specific services and access interfaces, and is relatively independent.

The TCP protocol is a fixed connection-based protocol that provides reliable data transmission between two computers. TCP can guarantee that the data will arrive when the data is sent from one end to the other end of the connection, and the order of the arriving data is the same as the order in which it was sent. Therefore, the TCP protocol is suitable for occasions with high reliability requirements. Reliable communication channels such as HTTP, FTP and Telnet are required.

UDP is a connectionless communication protocol that does not guarantee reliable data transmission, but can send data to several destinations and receive data from several sources. UDP does so by sending packets independently. The UDP protocol is suitable for some occasions that do not require high data accuracy. Some firewalls and routers are set to not allow UDP packet transmission. If you encounter problems with UDP connections, you can first determine whether to allow the use of the UDP protocol.

Generally speaking, a computer has only a single "physical connection" to the network, through which all data is sent to or from a specific computer, this is the port. The port is specified as an integer between 0 and 65535. The port of HTTP service is 80, and the port used by FTP is 21. The port number between 0 and 1023 is used for some well-known network services and applications. The user's ordinary network application should use the port number above 1024 to avoid port numbers. Used by another application or system service.

Sockets in network programs are used to connect applications to ports. A socket is an imaginary connection device. Java abstracts sockets into classes, and programmers only need to create Socket class objects to use sockets.

TCP network programming refers to the use of the Socket class to write communication programs. There are two application programs that communicate using the TCP protocol, one is called the server program and the other is called the client program. (1) The server program creates a ServerSocket (server-side socket) and calls the accept() method to wait for the client to connect; (2) The client program creates a Socket and requests to establish a connection with the server; (3) The server receives the connection from the client request, while creating a new Socket to establish a connection with the client. The server continues to wait for new requests.

Hosts on the Internet represent addresses in two ways, namely domain names and IP addresses. The InteAddress class object in the java.net package contains the domain name and IP address of an Internet host address.

Common methods of the InteAddress class
method return value Function description
getByName(String host) InetAddress Determine the IP address of a host given a hostname
getHostAddress() String Returns the IP address string
getHostName() String Get the hostname of this IP address
get(int index) Object The object used to get the specified index position
getLocalHost() InetAddress Returns an InetAddress object for localhost
toString() String Convert this IP address to String

The getByName() method provided by the InetAddress class can pass a domain name or IP address to the parameters of this method to obtain an InetAddress object, which contains the domain name and IP address of the host address.

ServerSocket is used to represent a server socket. The server socket waits for a connected socket through the specified port, and its main function is to wait for a connection "request" from the network. It can wait for a connected socket through the specified port. A server socket can be connected to a socket one at a time. If multiple clients make connection requests at the same time, the server socket will store the client requesting connection in the queue, and then take a socket out of it and connect it with the socket newly created by the server. If the number of requested connections is greater than the maximum capacity, the excess connection requests will be rejected. The default size of the queue is 80.

The construction methods of the ServerSocket class are: (1) Create an unbound server socket: new ServerSocket(); (2) Create a server socket bound to a specific port: new ServerSocket(int port); (3) Specify The maximum length of the queue, create a socket bound to the specified local port number: new ServerSocket(int port, int backlog).

Common methods of ServerSocket class
method return value illustrate
accept() Socket Wait for the client to connect, if connected, create a socket
isBound() boolean Determine the binding status of ServerSocket
getInetAddress() InetAddress Returns the local address of this server socket
isClosed() boolean Returns the closed status of the server socket
close() void close server socket
bind(SocketAddress endpoint) void Bind ServerSocket to pending address (IP address and port number)
getInetAddress() int Returns the port number the server socket is waiting on

Calling the accept() method of the ServerSocket class will return a Socket object connected to the core client Socket object. The output stream obtained by the Socket object on the server side using the getOutputStream() method will point to the input stream obtained by the client Socket object using the getInputStream() method. ; Similarly, the input stream obtained by the server's Socket object using the getInputStream() method will point to the input stream obtained by the client Socket object using the getOutputStream() method. That is, when the server writes information to the output stream, the client can read it through the corresponding input stream, and vice versa.

The accept() method blocks the continued execution of the thread until the client's call is accepted. If there is no client request, the accept() method does not block, then there is a problem with the program. Usually, a port number that is still occupied by other programs is used, resulting in unsuccessful ServerSocket binding.

Socket类是套接字类,使用Socket时,需要制定待连接服务器的IP地址和端口号。客户机创建了Socket对象以后,会向指定的IP地址及端口尝试连接。服务器套接字会创建新的套接字,与客户端套接字建立连接。服务端套接字与客户端套接字成功连接后,则可以获取套接字的输入/输出流,进行数据交换。

Socket类常用的构造方法:(1)创建到服务器连接的套接字:new Socket(String host,int port);(创建Socket时,可能发生IOException与UnknownHostException异常,需要捕获该异常)(2)通过InetAddress类创建Socket类对象。

Socket类的常用方法
方法 返回值 说明
getInetAddress() InetAddress 获取套接字连接的地址
getPort() int 获取此套接字连接的端口
getLocalAddress() InetAddress 获取套接字绑定的本地地址
getLocalPort() int 获取套接字绑定的本地端口
close() void 关闭套接字连接
getInputStream() InputStream 获取套接字的输入流
getOutputStream() OutputStream() 获取套接字的输出流

若客户机要连接服务器,则服务器必须首先做出动作。因此运行程序时一定要先启动服务端程序在运行客户端程序。

基于UDP通信的基本模式:(1)将数据打包,称为数据包。然后将数据包发往目的地。(2)接收别人发来的数据包,然后查看数据包。

发送数据包的步骤:(1)使用DatagramSocket()创建一个数据包套接字。(2)使用DatagramPacket(byte[] buf,int offset,int length,InetAddress address,int port)创建要发送的数据包。(3)使用DatagramSocket类的send()方法发送数据包。

接收数据包的步骤:(1)使用DatagramSocket(int port)创建数据包套接字,绑定到接口的端口。(2)使用DatagramPacket(byte[] buf,int length)创建字节数组来接收数据包。(3)使用DatagramPacket类的receive()方法,接收UDP包。

DatagramSocket类建立UDP程序的套接字,数据报套接字是包投递服务的发送或接收点。每个在数据报套接字上发送或接收的包都是单独编制和路由的。常用的构造方法有:(1)创建绑定到本地主机上任何可用的端口:new DatagramSocket();(2)创建绑定到本地主机上的指定端口的套接字。new DatagramSocket(int port);(3)创建绑定到指定本地地址,该方法适用于多块网卡和多个IP地址:new DatagramSocket(int port,InetAddress address)。

在接收程序时必须指定一个端口号,不要让系统随机产生。在发送程序时,系统自动分配即可。

DatagramPacket类表示数据报包,数据报包用来实现无连接包投递服务。每条报文仅根据该包中包含的信息,从一台机器路由到另一台机器。从一台机器发送到另一台机器的多个包可能选择不同的路由,也可能按不同的顺序达到。该类不对包投递做出保证。构造方法有:(1)接收指定长度,创建DatagramPacket实例:new DatagramPacket(byte[] buf ,int length);(2)指定数据包的内存空间和大小,同时指定数据包的目标地址和端口:new DatagramPacket(byte[] buf,int length,InetAddress address,int port);。

要广播或接收广播的主机地址必须加入到一个组内,地址在224.0.0.0~224.255.255.255之间,这类地址并不代表某个特定主机的位置。加入到同一个组的主机可以在某个端口上广播信息,也可以在某个端口上接收信息。

在进行应用程序开发时,经常需要选择文件,这些操作可以通过javax.swing.JFileChooser类来实现。JFileChoose类为用户选择文件提供了一个简单的机制。通过该类创建的对象是一个文件选择对话框,可以方便地选择文件。常用构造方法有:

JFileChooser类的常用构造方法
构造方法 说明
JFileChooser() 构造一个指向用户默认目录的文件选择对话框
JFileChooser(File f) 使用给定的File作为路径来构造一个文件选择对话框
JFileChooser(String dir) 构造一个使用给定路径的选择对话框

JFileChooser类还提供了两个比较重要的字段,即APPROVE_OPTION和CANCEL_OPTION,分别用于判断用户在文件选择对话框中是单击了“打开”按钮还是单击了“取消”按钮。如果单击了“打开”按钮,表示用户选择了文件,可以对文件进行操作;如果单击的是“取消”按钮,表示用户没有选择文件。

JFileChooser类的实例调用showOpenDialog(Component parent)方法,可以打开一个文件选择对话框,该方法返回一个整数值,用于判断用户是否选择了文件。如果选择了文件就可以通过getSelectFile()方法获得所选文件的File对象,通过File对象就可以获得文件的相关属性,如文件名、路径、文件大小等信息。

JFileChooser类提供了setFileFilter(FileFilter filter)方法,通过该方法可以为文件选择对话框设置过滤器,使其只显示指定类型的文件。该方法的参数类型javax.swing.filechooser.FileFilter是一个抽象类,实现该类可以对文件进行过滤。

FileFilter类的方法的说明
方法 说明
public abstract boolean accept(File file) 用于对文件进行过滤
public abstract String getDescription() 返回文件选择对话框中文件类型的描述信息

javax.swing.filechooser.FileNameExtensionFilter类实现了FileFilter类。FileNameExtensionFilter有一个构造方法,通过该类的实例可以实现文件的过滤操作。

FileNameExtensionFilter类的构造方法的说明
构造方法 说明
FileNameExtensionFilter(String description,String...extensions) 第一个参数description是文件选择对话框中文件类型的描述信息,第二个参数extensions是文件选择对话框中允许显示的文件类型,多个文件类型用逗号隔开。

Swing提供的javax.swing.JToolBar类创建的对象就是一个工具栏。可以放在窗体默认布局的顶部、左侧、右侧或底部,也可位于窗体之外,称为浮动工具栏。其构造方法有:

JToolBar类的常用构造方法说明
构造方法 说明
JToolBar() 构造一个默认位置为水平方向的工具栏
JToolBar(String name) 构造一个默认位置为水平方向,当工具栏称为浮动状态时具有指定的标题。

JToolBar类也提供了操作工具栏的方法,可以实现工具栏的设置。

JToolBar类的常用方法的说明
方法 说明
Component add(Component c) 在工具栏的末尾添加参数指定的组件,如按钮、复选框和单选按钮等
void addSeparator() 将默认大小的分隔符添加到工具栏的末尾
void addSeparator(Dimension size) 将指定大小的分隔符添加到工具栏的末尾

使用类javax.swing.JMenuBar创建的对象是一个菜单栏,菜单栏只有一个无入口参数的构造方法JMenuBar(),用于构造一个菜单栏。JMenuBar类用于创建菜单栏,创建了菜单栏就可以调用该类的add(JMenu menu)方法把菜单添加到菜单栏上。

JMenu类的常用构造方法
构造方法 说明
JMenu() 构造一个没有初始文本的新菜单
JMenu() 构造一个初始文本为指定字符串的新菜单

JMenu类的常用方法的说明
方法 说明
add(JMenuItem menuItem) 将某个菜单项追加到此菜单的末尾
insert(JMenuItem menuItem,int pos) 在给定位置插入指定的菜单项
addSeperator() 将新分隔符追加到菜单的末尾
insertSeparator(int pos) 在指定的位置插入分隔符

类javax.swing.JMenuItem创建的对象是一个菜单项,当用户选择某个菜单项时,将触发该菜单项的动作事件,进而完成该菜单项的功能。

JMenuItem类的常用构造方法的说明
构造方法 说明
JMenuItem() 创建不带有文本和图标的菜单项
JMenuItem(String text) 创建带有指定文本的菜单项
JMenuItem(Icon icon) 创建带有指定图标的菜单项
JMenuItem(String text,Icon icon) 创建带有指定文本和图标的菜单项

使用JMenuItem类的setIcon(Icon icon)方法可以为菜单项添加图标。菜单项通常使用动作事件监听器来捕获动作事件,进而实现相应的业务逻辑。为菜单项添加动作事件监听器可以使用addActionListener(ActionListener action Listener)方法来实现。

使用类javax.swing.JPopupMenu创建的对象是弹出菜单,弹出菜单是一种特殊形式的菜单,并不固定在窗体中的某个位置,而是由鼠标指针决定弹出菜单出现的位置。

JPopupMenu类的构造方法的说明
构造方法 说明
JPopupMenu() 构造一个新的弹出菜单
JPopupMenu() 构造一个有标题的弹出菜单
JPopupMenu类的常用方法的说明
方法 说明
add(JMenuItem menuItem) 将指定菜单项添加到弹出菜单的末尾
addSeparator() 将新分隔符添加到菜单的末尾
isPopupTrigger(MouseEvent e) 如果弹出菜单的当前组件将鼠标事件视为弹出菜单的触发器,则返回true
show(Component parent,int x, int y) 在组件调用这坐标空间中的位置(x,y)处显示弹出菜单

分割面板可以将其所在的区域分割为两部分,这两部分之间有一个分隔条,通过调整分隔条的位置可以改变这两部分的相对大小,用户可以根据需要进行调整。并且分割条两侧的每一部分中还可以再放分割面板,实现分割面板的嵌套使用。

使用javax.swing.JSplitPane类创建的对象是一个分割面板。

JSplitPane类的常用构造方法的说明
构造方法 说明
JSplitPane() 创建一个在水平方向进行分割、无连续布局、为组件使用两个按钮的分割面板
JSplitPane(int orient) 创建一个在指定方向上进行分割且无连续布局的分割面板
JSplitPane(int orient,Component leftC,Component rightC) 创建一个在指定方向上进行分割、不连续重绘的指定组件的分割面板

在JSplitPane类的构造方法上有一个参数orient,该参数用来指定分割面板分割方向的字段。

JSplitPane类的分割方向字段说明
字段 说明
JSplitPane.HORIZONCAL_SPLIT 表示在水平方向上对分割面板进行分割
JSplitPane.VERTICAL_SPLIT 表示在垂直方向上对分割面板进行分割

JSplitPane类创建的对象是一个分割面板,JSplitPane类提供了一些方法,可以完成分割面板的常用操作。

JSplitPane类的常用方法说明
方法 说明
getBottomComponent() 返回分隔条下面或者右边的组件
getDividerSize() 返回分隔条的大小
getLeftComponent() 返回分隔条左边或者上面的组件
getRightComponent() 返回分隔条右边或者下面的组件
getTopComponent() 返回分隔条上面或者左边的组件
remove(Component component) 移除分割面板中的子组件component
setBottomComponent(Component component) 将组件设置到分割条的下面或者右边
setLeftComponent(Component component) 将组件设置到分隔条的左边或者上面
setRightComponent(Component component) 将组件设置到分隔条的右边或者下面
setTopComponent(Component  component) 将组件设置到分隔条的上面或者左边
setDividerLocation(int location) 设置分隔条的位置
setDiverSize(int newSize) 设置分割条的大小
setOneTouchExpandable(boolean newValue) 参数设置true时,可以使分割面板在分隔条上提供一个UI小部件来快速展开/折叠分隔条
setOrientation(int orient) 设置分割方向

选项卡面板可以方便地在窗体上放置多个标签页,每个标签页相当于一个与此选项卡大小相同的容器,可以在每个标签页的容器中放置一个或多个组件,使每个标签页实现不同的界面效果。

使用javax.swing.JTabblePane类创建的对象是一个选项卡面板,它允许用户通过单击具有给定标题和/或图标的选项卡,在一组组件之间进行切换。

JTabbedPane类的构造方法的说明
构造方法 说明
JTabbedPane() 创建一个在上面显示标签、布局方式为自动换行的空选项卡
JTabbedPane(int tabPlace) 创建一个在指定位置显示标签、布局方式为自动换行的空选项卡
JTabbedPane(int tabPlace,int tabLayout) 创建一个在指定位置显示标签和具有指定布局策略的空选项卡

在JTabbedPane类的构造方法中的参数tabPlace是用于指定选项卡面板显示位置的字段,参数tabLayout是指定选项卡面板标签布局的字段。

JTabbedPane类的常用字段说明
字段 说明
JTabbedPane.TOP 表示在选项卡的上边位置显示标签
JTabbedPane.BOTTOM 表示在选项卡的下边位置显示标签
JTabbedPane.LEFT 表示在选项卡的左侧位置显示标签
JTabbedPane.RIGHT 表示在选项卡的右侧位置显示标签
JTabbedPane.WRAP_TAB_LAYOUT 当选项卡的一行或一列不能显示所有标签时,标签会自动机换行或换列
JTabbedPane.SCROLL_TAB_LAYOUT 当选项卡的一行或一列标签显示不全时,标签行或列会出现UI调节组件

JTabbedPane类创建的对象是一个选项卡面板。

JTabblePane类的常用方法的说明
方法 说明
addTab(String title,Component component) 添加一个标题为title且没有图标的选项卡
addTab(String title ,Icon icon,Component component) 添加一个标题为title,图标为icon的选项卡
addTab(String title,Icon icon,Component component,String tip) 添加一个标题为title,图标为icon,工具提示文本为tip的选项卡
getSelectedComponent() 返回选项卡面板中当前选择的选项卡
getSelectedIndex() 返回当前选择的选项卡的索引
getTabComponentAt(int index) 返回索引值为index位置上的选项卡
getTabCount() 返回选项卡面板的选项卡数
getTitleAt(int index) 返回索引值为index位置的选项卡标题
getToolTipTextAt(int index) 返回索引值为index位置的选项卡工具提示文本
insertTab(String title,Icon icon,Component component,String tip,int index) 在索引值为index位置插入一个标题为title,图标为icon,工具提示文本为tip的选项卡
setIconAt(int index,Icon icon) 将索引值为Index位置的图标设置为icon
setSelectedComponent(Component c) 设置此选项卡面板的已选组件
setSelectedIndex(int index) 设置索引值为index位置处的选项卡为当前选项卡
setTitleAt(int index,String title) 将索引值为index位置的选项卡标题设置为true
setToopTipTextAt(int index,String toolTipText) 将索引值为index位置的工具提示文本设置为toolTipText

桌面面板和内部窗体用于创建多文档界面。多文档界面的特点是存在一个外部窗体(也称为主窗体),在外部窗体中可以有多个内部窗体,内部窗体支持拖动、关闭、图标化、调整大小、标题显示和菜单栏等功能;当外部窗体最小化时,内部窗体会被隐藏,当拖动内部窗体时,不能拖出外部窗体。多文档界面的创建方法是首先创建桌面面板,然后把桌面面板放到JFrame窗体内容面板默认布局的中央,在创建内部窗体,并把内部窗体放到桌面面板容器中。

The javax.swing.JDesktopPane class is a container for multiple document interfaces or virtual desktops. The object created by this class is a desktop panel. The JDesktopPane class has only one constructor without entry parameters, JDesktopPane(), which is used to construct a desktop panel container.

The object created by the JDesktopPane class is a desktop panel, and the methods provided by this class can conveniently operate the internal forms and components in the desktop panel.

Description of common methods of JDesktopPane class
method illustrate
JInternalFrame [] getAllFrames () Returns all inner forms currently displayed in the desktop panel
JInternetFrame getSelectedFrame () Returns the currently active inner form in this desktop panel. Returns null if there is currently no active inner form
void remove(int index) Removes the specified indexed component from this desktop panel
void removeAll() Remove all components from this desktop panel
JInternalFrame selectFrame(boolean b) Select the next inner form in this desktop panel
void setDragMode(int dragMode) Sets the "drag style" used by the desktop panel
void setSelectedFrame(JInternalFrame f) Sets the currently active inner form in this desktop panel

The object created by the class JDesktopPane is a desktop panel. Use the setDragMode method of this class to set the drag mode of the internal form:

JDesktopPane class to set the field description of the drag mode
field illustrate
JDesktopPane.LIVE_DRAG_MODE Indicates that all content of the item being dragged appears inside the desktop panel, which is the default
JDesktopPane.OUTLINE_DRAG_MODE An outline representing the item being dragged appears inside the desktop panel
In order to make the multi-document interface beautiful, you can add a background image to the desktop panel by creating a label with a background image and placing the label at the bottom of the desktop panel container.

new Integer(Integer.MIN_VALUE)用于指定把标签放在桌面面板最底层的关键代码,这样标签就可以显示在所有内部窗体的后方,而不会遮挡住内部窗体。

javax.swing.JInternalFrame类创建的对象是一个内部窗体。

JInternalFrame类的构造方法的说明
构造方法 说明
JInternalFrame() 创建不可调整大小、不可关闭、不可最大化、不可图标化、没有标题的内部窗体
JInternalFrame(String title) 创建不可调整大小、不可关闭、不可最大化、不可图标化,具有指定标题的内部窗体
JInternalFrame(String title,boolean resizable) 创建不可调整大小、不可关闭、不可图标化,具有指定标题和可调整大小的内部窗体
JInternalFrame(String title,boolean resizable,boolean choseable) 创建不可最大化、不可图标化,具有指定标题、可调整大小及可关闭的内部窗体
JInternalFrame(String title,boolean resizable,boolean choseable,boolean maximizable) 创建不可图标化,具有指定标题、可调整大小及可关闭和可最大化的内部窗体
JInternalFrame(String title,boolean resizable,boolean choseable,boolean maximizable,boolean iconifiable) 创建具有指定标题、可调整大小、可关闭、可最大化和可图标化的内部窗体

JInternalFrame类创建的是一个内部窗体,该类提供的方法可以实现对内部窗体的常用设置:

JInternalFrame类的常用方法的说明
方法 说明
void dispose() 使此内部窗体不可见,取消选定并关闭它
String getTitle() 返回内部窗体的标题
boolean isClosed() 返回此内部窗体当前是否已关闭
boolean isIcon() 返回内部窗体当前是否已图标化
boolean isSelected() 返回内部窗体当前是否为选定的或处于激活状态
void setFrameIcon(Icon icon) 设置要在此内部窗体的标题栏中显示的图像
void setMaximizable(boolean b) 设置内部窗体是否提供最大化按钮
void setJMenuBar(JMenuBar m) 设置内部窗体的菜单栏
void setResizable(Boolean b) 设置是否可以调整内部窗体的大小
void setSelected(boolean selected) 设置是否激活此内部窗体
void setTitle(String title) 设置内部窗体的标题
void show() 如果内部窗体不可见,则将该内部窗体置于前端,使其可见并尝试选定它
void toBack() 将此内部窗体发送至后台
void toFrond() 将此内部窗体置于前端

系统托盘是个特殊的区域,能够驻留程序,被运行在桌面上的所有程序共享,通常显示在桌面的底部。

java.awt.SystemTray类表示桌面的系统托盘,该类提供了一些方法可以实现托盘操作。

SystemTray类的常用方法的说明
方法 说明
void add(TrayIcon trayIcon) 将托盘图标添加到系统托盘
static SystemTray getSystemTray() 获取表示桌面托盘区的系统托盘实例
TrayIcon[] getTrayIcons() 返回由此应用程序添加到托盘中的所有图标的数组
Dimension getTrayIconSize() 返回托盘图标在系统托盘中占用的空间大小(以像素为单位)
static boolean isSupported() 返回当前平台是否支持系统托盘
void remove(TrayIcon trayIcon) 从系统托盘中移除指定的托盘图标

When using the system tray, first use the isSupported() method of the SystemTray class to determine whether the current system supports the system tray. If it supports the system tray, you can use the getSystemTray() method of the SystemTray class to get the system tray.

The system tray can contain one or more tray icons. You can use the add(TrayIcon trayIcon) method of the SystemTray class to add a tray icon to the system tray. When the tray is no longer needed, you can use the remove(TrayIcon trayIcon) method of the SystemTray class to remove the tray icon. Icon removed.

The object created by the java.awt.TrayIcon class is a tray icon.

Description of the constructor of the TrayIcon class
method illustrate
TrayIcon(Image image) Create a tray icon with the specified image
TrayIcon(Image image,String tooltip) Create a tray icon with specified image and tooltip text
TrayIcon(Image image,String tooltip,PopupMenu popup) Create a tray icon with specified image, tooltip text and popup menu

Description of common methods of TrayIcon class
method illustrate
addMouseListener(MouseListener listener) Add a mouse listener to receive mouse events from this tray icon
setImage (image image) Set the image of the tray icon
setPopupMenu(PopupMenu popup) Set the popup menu for the tray icon
setToolTip(String tooltip) Sets the tooltip text for the tray icon
Two ways to add a popup menu: (1) Implemented by the construction method of the three entry parameters of the tray icon: public TrayIcon(Image image, String tooltip, PopupMenu popup); (2) Implemented by the setPopupMenu method of the tray icon. setPopupMenu(PopupMenu popup)


Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325646155&siteId=291194637