本文主要记录了使用IntelliJ IDEA Community Edition + Java Swing编写UDP接收工具的过程中遇到的问题。
程序运行效果如下:
监听端口文本框只能输入数字:
开始监听后,输入文本框变灰色,退出时弹出提示框,确认退出后,关闭端口。
接收到数据后进行显示:
在完成本程序的过程中,遇到的问题有:
在对同一个Thread对象执行两次start()方法时,会出现该异常。
例如下面的代码:
Thread thr = new Thread(new Runnable() {
@Override
public void run() {
...
}
});
thr.start();
...
thr.start(); //同一个Thread对象两次start会出现异常IllegalThreadStateException
查看Thread类的start()方法的源码,可以看到如下代码:
if (threadStatus != 0)
throw new IllegalThreadStateException();
这里的0是一个代表线程状态的常量,查看Thread类的Enum类State:
public enum State {
/**
* Thread state for a thread which has not yet started.
*/
NEW,
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
RUNNABLE,
/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
BLOCKED,
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
*
* - {@link Object#wait() Object.wait} with no timeout
* - {@link #join() Thread.join} with no timeout
* - {@link LockSupport#park() LockSupport.park}
*
*
* A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called {@code Object.wait()}
* on an object is waiting for another thread to call
* {@code Object.notify()} or {@code Object.notifyAll()} on
* that object. A thread that has called {@code Thread.join()}
* is waiting for a specified thread to terminate.
*/
WAITING,
/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
*
* - {@link #sleep Thread.sleep}
* - {@link Object#wait(long) Object.wait} with timeout
* - {@link #join(long) Thread.join} with timeout
* - {@link LockSupport#parkNanos LockSupport.parkNanos}
* - {@link LockSupport#parkUntil LockSupport.parkUntil}
*
*/
TIMED_WAITING,
/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
TERMINATED;
}
可以看到,只有线程状态为NEW的线程,才允许使用start()方法。
否则会抛出异常。
每次执行start方法之前,才new一个Thread对象。
例如,在类里定义了一个Thread对象mRecThr,并且该线程需要多次启动,则应该如下定义:
public class UDP {
...
private Thread mRecThr;
...
public void startReceive() {
mRecFlag = true;
mRecThr = new Thread(new Runnable() {
public void run() {
...
}
});
mRecThr.start(); //WARNING:同一个线程启动两次,会出现异常IllegalThreadStateException
}
...
}
即保证每次start时,threadStatus都是0。
实现这个效果的方法有很多,这里使用重写PlainDocument
类的insertString()
方法的方式来实现。
首先,先自定义一个继承PlainDocument
类的子类NumberTextField
并重写insertString()
方法:
class NumberTextField extends PlainDocument {
public NumberTextField() {
super();
}
public void insertString(int offset, String str, AttributeSet attr)
throws javax.swing.text.BadLocationException {
if (str == null) {
return;
}
char[] s = str.toCharArray();
int length = 0;
// 过滤非数字
for (int i = 0; i < s.length; i++) {
if ((s[i] >= '0') && (s[i] <= '9')) {
s[length++] = s[i];
}
// 插入内容
super.insertString(offset, new String(s, 0, length), attr);
}
}
}
再使用要设置的JTextField对象的setDocument方法应用修改:
textField1.setDocument(new NumberTextField()); //使用该方法让文本框只能接受数字
使用JOptionPane
的showMessageDialog
方法来实现这个效果。
打开JOptionPane.java,我们可以看到该方法存在着多个重载。
以下面的方法为例,说明各参数的含义:
public static void showMessageDialog(Component parentComponent,
Object message, String title, int messageType)
throws HeadlessException {
...
}
Component parentComponent
:指定弹出对话框的位置,若传入一个Frame对象,则弹出对话框的位置会在该Frame的中心。若置为null,则弹出对话框在屏幕中心。
Object message
:要显示的信息。
String title
:对话框标题。
int messageType
:对话框的提示图标,在JOptionPane类中有如下常量定义:
//
// Message types. Used by the UI to determine what icon to display,
// and possibly what behavior to give based on the type.
//
/** Used for error messages. */
public static final int ERROR_MESSAGE = 0;
/** Used for information messages. */
public static final int INFORMATION_MESSAGE = 1;
/** Used for warning messages. */
public static final int WARNING_MESSAGE = 2;
/** Used for questions. */
public static final int QUESTION_MESSAGE = 3;
/** No icon is used. */
public static final int PLAIN_MESSAGE = -1;
综上所述,下列代码的执行结果如图:
JOptionPane.showMessageDialog(null, "端口号错误,请输入0~65535之间的端口号。", "Error", JOptionPane.ERROR_MESSAGE);
要自定义点击“关闭窗口”按钮时的操作,首先需要取消关闭按钮的功能,然后自己完成点击关闭窗口按钮的事件。
关键代码如下:
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); //自定义关闭窗口动作
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
if(udpService.isRunning()) {
int flag =
JOptionPane.showConfirmDialog(null,"正在监听,是否退出?",
"退出",JOptionPane.YES_NO_OPTION,
JOptionPane.INFORMATION_MESSAGE);
if(flag == JOptionPane.NO_OPTION) {
return;
} else {
udpService.stopUDPListen();
System.exit(0);
}
}
System.exit(0);
}
});
如上述代码所示。
首先设置关闭窗口动作为DO_NOTHING_ON_CLOSE
(即点击时系统不执行任何操作)
之后在该窗口上添加自定义的WindowAdapter
对象,重写windowClosing
方法。
在该方法中添加自己需要的逻辑即可。
若要关闭程序,请使用System.exit(0)
JOptionPane.showConfirmDialog(null,"正在监听,是否退出?",
"退出",JOptionPane.YES_NO_OPTION,
JOptionPane.INFORMATION_MESSAGE);
使用如上代码即可显示确认对话框。
其中的JOptionPane.YES_NO_OPTION
常量用于控制确认对话框上显示的按钮。
其他常量如下:
//
// Option types
//
/**
* Type meaning Look and Feel should not supply any options -- only
* use the options from the JOptionPane
.
*/
public static final int DEFAULT_OPTION = -1;
/** Type used for showConfirmDialog
. */
public static final int YES_NO_OPTION = 0;
/** Type used for showConfirmDialog
. */
public static final int YES_NO_CANCEL_OPTION = 1;
/** Type used for showConfirmDialog
. */
public static final int OK_CANCEL_OPTION = 2;
可通过监听返回值获得用户按下的按钮是什么,可能的返回值如下所示:
//
// Return values.
//
/** Return value from class method if YES is chosen. */
public static final int YES_OPTION = 0;
/** Return value from class method if NO is chosen. */
public static final int NO_OPTION = 1;
/** Return value from class method if CANCEL is chosen. */
public static final int CANCEL_OPTION = 2;
/** Return value form class method if OK is chosen. */
public static final int OK_OPTION = 0;
/** Return value from class method if user closes window without selecting
* anything, more than likely this should be treated as either a
* CANCEL_OPTION
or NO_OPTION
. */
public static final int CLOSED_OPTION = -1;
耗时操作请使用子线程完成。
更新UI的操作请用Runnable包装,并使用invokeLater执行。
下面是一个用子线程从阻塞队列中获取UDP传来的数据并刷新UI的例子。
thrGetData = new Thread(new Runnable() {
@Override
public void run() {
while (MainClass.udpService.isRunning()) {
DatagramPacket dp = MainClass.udpService.getDataFromQueue();
//耗时操作,从阻塞队列中获取数据,队列为空时线程阻塞
if (dp == null)
return;
byte[] recv = new byte[dp.getLength()];
System.arraycopy(dp.getData(), 0, recv, 0, dp.getLength());
final String data = new String(recv);
javax.swing.SwingUtilities.invokeLater(new Runnable() {
//使用invokeLater来更新UI
public void run() {
recData.setText(data);
}
});
}
}
});
thrGetData.start();