java.awt.Robot.waitForIdle()是否等待调度事件?

我正在使用java.awt.Robot进行Swing应用程序的集成测试,但是我无法按正确的顺序运行我的操作。 如何判断调用robot.mousePressed(...)的线程是否阻塞,直到Swing完成调度该事件为止? 显然, robot.setAutoWaitForIdle(true)没有用。

这是我的演示。 我期待“机器人完成!” 消息总是在“动作完成阻止”之后出现,但相反,它经常发生得太快。

 import java.awt.AWTException; import java.awt.GraphicsConfiguration; import java.awt.GraphicsDevice; import java.awt.GraphicsEnvironment; import java.awt.Point; import java.awt.Rectangle; import java.awt.Robot; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.InputEvent; import java.sql.Date; import java.text.DateFormat; import java.util.logging.ConsoleHandler; import java.util.logging.Formatter; import java.util.logging.LogManager; import java.util.logging.LogRecord; import java.util.logging.Logger; import javax.swing.GroupLayout; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.SwingUtilities; import javax.swing.WindowConstants; public class RobotWaitForIdleDemo { /** * Create the device that contains the given point in screen coordinates. * Robot has to be constructed differently for each monitor. */ public static GraphicsDevice getDevice(Point p) { GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); GraphicsDevice[] gs = ge.getScreenDevices(); // Search the devices for the one that draws the specified point. for (GraphicsDevice device : gs) { GraphicsConfiguration configuration = device.getDefaultConfiguration(); Rectangle bounds = configuration.getBounds(); if (bounds.contains(p)) { return device; } } return null; } public static final Logger logger = Logger.getLogger(RobotWaitForIdleDemo.class.getName()); public static void main(String[] args) { LogManager.getLogManager().reset(); Formatter formatter = new Formatter() { @Override public String format(LogRecord arg0) { Date date = new Date(arg0.getMillis()); DateFormat.getTimeInstance().format(date); return String.format("%s %s %s %s%n", DateFormat.getTimeInstance().format(date), arg0.getLoggerName(), arg0.getLevel(), arg0.getMessage()); } }; ConsoleHandler consoleHandler = new ConsoleHandler(); consoleHandler.setFormatter(formatter); logger.addHandler(consoleHandler); final JFrame jframe = new JFrame("Robot experiment"); GroupLayout groupLayout = new GroupLayout(jframe.getContentPane()); final JButton jbutton = new JButton("Click me!"); jbutton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { // Simulate a heavy Swing event handler. logger.info("(swing thread) Action starting to block..."); try { Thread.sleep(500); } catch (InterruptedException e1) {} logger.info("(swing thread) Action finished blocking."); } }); JButton tryAgainBUtton = new JButton("Automatically click above button."); tryAgainBUtton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { new Thread(new Runnable() { @Override public void run() { try { Point point = new Point(jbutton.getWidth()/2,jbutton.getHeight()/2); SwingUtilities.convertPointToScreen(point, jbutton); GraphicsDevice device = getDevice(point); Point offset = device.getDefaultConfiguration().getBounds().getLocation(); Robot robot = new Robot(device); robot.setAutoWaitForIdle(true); robot.setAutoDelay(30); robot.mouseMove(point.x - offset.x, point.y - offset.y); String threadName = Thread.currentThread().getName(); logger.info(String.format("(%s) robot.mousePress(%d)", threadName, InputEvent.BUTTON1_MASK)); robot.mousePress(InputEvent.BUTTON1_MASK); logger.info(String.format("(%s) robot.mouseRelease(%d)", threadName, InputEvent.BUTTON1_MASK)); robot.mouseRelease(InputEvent.BUTTON1_MASK); logger.info(String.format("(%s) robot finished!", threadName, InputEvent.BUTTON1_MASK)); } catch (AWTException ex) { ex.printStackTrace(); } } }, "robot thread").start(); } }); jframe.getContentPane().setLayout(groupLayout); groupLayout.setAutoCreateGaps(true); groupLayout.setAutoCreateContainerGaps(true); groupLayout.setVerticalGroup( groupLayout.createSequentialGroup() .addComponent(jbutton) .addComponent(tryAgainBUtton)); groupLayout.setHorizontalGroup( groupLayout.createParallelGroup() .addComponent(jbutton) .addComponent(tryAgainBUtton) ); jframe.setSize(300, 300); jframe.setVisible(true); jframe.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); } } 

我在Ubuntu上运行Java 1.6。

也许这个可以帮助你,注意没有在Java7中测试过

你可以在isEventDispatchThread()的每个步骤中测试它

 import java.awt.*; import java.awt.event.*; import javax.swing.*; import sun.awt.SunToolkit; public class TestMenu { /** * Without a delay, SunToolkit may encounter a problem in SunToolkit (at * least in JDK 6, where the drop down size problem is not present). * * Note: SunToolkit also has some mechanism to delay, but I forgot how it * worked. * * 
 * Exception in thread "main" sun.awt.SunToolkit$InfiniteLoop * at sun.awt.SunToolkit.realSync(Unknown Source) * at TestMenu.syncAndDelay(TestMenu.java:172) * at TestMenu.click(TestMenu.java:88) * at TestMenu.moveAndClickCenter(TestMenu.java:150) * at TestMenu.main(TestMenu.java:45) * 

* * As a bonus, the delay makes the scenario better visible for the human * eye. */ private static int delay = 500; private static JMenu[] menus = new JMenu[5]; private static Dimension[] parentSizes; private static Robot robot; private static SunToolkit toolkit; public static void main(String[] args) throws Exception { robot = new Robot(); toolkit = (SunToolkit) Toolkit.getDefaultToolkit(); parentSizes = new Dimension[menus.length]; createGUI(); // Open the first menu. Then get the drop down size of all menu's moveAndClickCenter(menus[0]); for (int index = 0; index < menus.length; index++) { parentSizes[index] = getDropDownSize(index); }// Click the last item on the last menu. Component item = menus[menus.length - 1].getMenuComponent(menus[menus.length - 1].getMenuComponentCount() - 1); moveAndClickCenter(item); // Open the last drop down again. Then get the drop down sizes once more. If size not equal to previous size, then it's a bug. boolean bug = false; moveAndClickCenter(menus[menus.length - 1]); for (int index = menus.length - 1; index >= 0; index--) { Dimension currentSize = getDropDownSize(index); System.out.print("old: " + parentSizes[index] + ", new: " + currentSize); if (!parentSizes[index].equals(currentSize)) { bug = true; System.out.println(" ERROR"); } else { System.out.println(); } } if (bug) { throw new RuntimeException("JMenu drop down size is changed for no reason."); } } private static Dimension getDropDownSize(int index) throws Exception { moveToCenter(menus[index]); return menus[index].getMenuComponent(0).getParent().getSize(); } private static void click() throws Exception { robot.mousePress(InputEvent.BUTTON1_MASK); robot.mouseRelease(InputEvent.BUTTON1_MASK); syncAndDelay(); } private static void createGUI() throws Exception { SwingUtilities.invokeAndWait(new Runnable() { @Override public void run() { UIManager.LookAndFeelInfo[] infos = UIManager.getInstalledLookAndFeels();// The L&F defines the drop down policy. for (final UIManager.LookAndFeelInfo info : infos) { if (info.getName().toLowerCase().indexOf("metal") >= 0) { if (!UIManager.getLookAndFeel().getName().equals(info.getName())) { try { UIManager.setLookAndFeel(info.getClassName()); System.out.println("Attempt to set look and feel to " + info.getName()); } catch (Exception e) { e.printStackTrace(); } } else { System.out.println("Metal look and feel is the default"); } break; } } System.out.println("Testing with " + UIManager.getLookAndFeel().getName()); // Setup the GUI. JFrame frame = new JFrame("A frame"); frame.setJMenuBar(new JMenuBar()); for (int menuIndex = 0; menuIndex < menus.length; menuIndex++) { menus[menuIndex] = new JMenu("Menu " + menuIndex); frame.getJMenuBar().add(menus[menuIndex]); for (int itemIndex = 0; itemIndex <= menus.length - menuIndex; itemIndex++) { // It seems that the problem only occurs if the drop down is displayed outside the frame at the right // (not sure though). A rather long item name. JMenuItem item = new JMenuItem("Menu " + menuIndex + " item " + itemIndex); menus[menuIndex].add(item); } } frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.pack(); frame.setLocationRelativeTo(null); frame.setVisible(true); } }); syncAndDelay(); } private static void moveAndClickCenter(Component c) throws Exception { moveToCenter(c); click(); } private static void moveToCenter(final Component c) throws Exception { final Point cp = new Point(); SwingUtilities.invokeAndWait(new Runnable() { @Override public void run() { Point p = new Point(c.getWidth() / 2, c.getHeight() / 2); SwingUtilities.convertPointToScreen(p, c); cp.setLocation(p); } }); robot.mouseMove(cp.x, cp.y); syncAndDelay(); } private static void syncAndDelay() throws Exception { if (delay > 0) { Thread.sleep(delay); } toolkit.realSync(); } private TestMenu() { } }

mKorbel的答案( SunToolkit.realSync() )是正确的,但是realSync很慢并抛出SunToolkit.InfiniteLoop 。 在研究了realSync之后我最终使用了这个变体:

 import java.awt.Toolkit; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Arrays; import java.util.List; import javax.swing.SwingUtilities; import sun.awt.SunToolkit; public class ToolkitUtils { private Method syncNativeQueue; private boolean isSyncNativeQueueZeroArguments; public ToolkitUtils() { syncNativeQueue = null; isSyncNativeQueueZeroArguments = true; try { // Since it's a protected method, we have to iterate over declared // methods and setAccessible. Method[] methods = SunToolkit.class.getDeclaredMethods(); for (Method method: methods) { String name = method.getName(); if ("syncNativeQueue".equals(name)) { List> parameterTypes = Arrays.asList(method.getParameterTypes()); if (Arrays.>asList(long.class).equals(parameterTypes)) { isSyncNativeQueueZeroArguments = false; } else if (parameterTypes.isEmpty() && null == syncNativeQueue) { isSyncNativeQueueZeroArguments = true; } else { continue; } syncNativeQueue = method; syncNativeQueue.setAccessible(true); } } } catch (SecurityException e) { throw new RuntimeException(e); } if (syncNativeQueue == null) throw new IllegalStateException("Could not find method SunToolkit.syncNativeQueue."); } /** * Block until Swing has dispatched events caused by the Robot or user. * * 

* It is based on {@link SunToolkit#realSync()}. Use that method if you want * to try to wait for everything to settle down (eg if an event listener * calls {@link java.awt.Component#requestFocus()}, * {@link SwingUtilities#invokeLater(Runnable)}, or * {@link javax.swing.Timer}, realSync will block until all of those are * done, or throw exception after trying). The disadvantage of realSync is * that it throws {@link SunToolkit.InfiniteLoop} when the queues don't * become idle after 20 tries. * *

* Use this method if you only want to wait until the direct event listeners * have been called. For example, if you need to simulate a user click * followed by a stream input, then you can ensure that they will reach the * program under test in the right order: * *

 * robot.mousePress(InputEvent.BUTTON1); * toolkitUtils.flushInputEvents(10000); * writer.write("done with press"); * 

* * @see {@link java.awt.Robot#waitForIdle()} is no good; does not wait for * OS input events to get to the Java process. * @see {@link SunToolkit#realSync()} tries 20 times to wait for queues to * settle and then throws exception. In contrast, flushInputEvents does * not wait for queues to settle, just to flush what's already on them * once. * @see {@link java.awt.Toolkit#sync()} flushes graphics pipeline but not * input events. * * @param syncNativeQueueTimeout * timeout to use for syncNativeQueue. Something like 10000 is * reasonable. */ public void flushInputEvents(long syncNativeQueueTimeout) { SunToolkit toolkit = (SunToolkit) Toolkit.getDefaultToolkit(); // 1) SunToolkit.syncNativeQueue: block until the operating system // delivers Robot or user events to the process. try { if (isSyncNativeQueueZeroArguments) { // java 1.6 syncNativeQueue.invoke(toolkit); } else { // java 1.7 syncNativeQueue.invoke(toolkit, syncNativeQueueTimeout); } } catch (IllegalArgumentException e) { throw new RuntimeException(e); } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { throw new RuntimeException(e); } // 2) SunToolkit.flushPendingEvents: block until the Toolkit thread // (aka AWT-XAWT, AWT-AppKit, or AWT-Windows) delivers enqueued events // to the EventQueue SunToolkit.flushPendingEvents(); // 3) SwingUtilities.invokeAndWait: block until the Swing thread (aka // AWT-EventQueue-0) has dispatched all the enqueued input events. try { SwingUtilities.invokeAndWait(new Runnable(){ @Override public void run() {}}); } catch (InterruptedException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { throw new RuntimeException(e); } } }