JavaMail:保持IMAPFolder.idle()活着

我正在制作一个程序,需要监控Gmail帐户中的新邮件,为了尽快得到它们,我使用的是JavaMail的空闲function。 这是我用来调用folder.idle()的线程的代码片段:

//Run method that waits for idle input. If an exception occurs, end the thread's life. public void run() { IMAPFolder folder = null; try { folder = getFolder(); while(true) { //If connection has been lost, attempt to restore it if (!folder.isOpen()) folder = getFolder(); //Wait until something happens in inbox folder.idle(true); //Notify controller of event cont.inboxEventOccured(); } } catch (Exception ex) { ex.printStackTrace(); } System.out.println("MailIdleWaiter thread ending."); } 

getFolder()方法基本上打开与IMAP服务器的连接并打开收件箱。

这有效一段时间,但在10分钟左右后它就会停止获取更新(不会抛出任何exception)。

我正在寻找保持连接活着的建议。 我是否需要第二个线程,其唯一的作用是睡眠并每10分钟更新一次idle()线程,还是有更简单/更好的方法?

提前致谢。

一个常见的错误是假设IDLE命令将无限期地发布更新。 但是,定义IDLE扩展的RFC 2177规定:

如果客户端运行了IDLE命令,则服务器可以认为客户端处于非活动状态,并且如果此类服务器具有不活动超时,则可以在其超时期限结束时隐式地注销客户端。 因此,建议使用IDLE的客户端终止IDLE并至少每29分钟重新发出一次以避免被注销。 这仍然允许客户端接收即时邮箱更新,即使它只需要半小时间隔“轮询”。

特别是GMail,如你所说,大约10分钟就会超时。

我们只需要每9分钟左右重新发出一次IDLE命令才能工作。 javax.mail API无法为IDLE命令设置超时,因此您需要第二个线程来移动它。

第一种方法是让第二个线程中断第一个线程,处理exception并忽略它。 然而,这将允许没有干净的方式来关闭线程,所以我不会推荐它。 更简洁的方法是让第二个线程向服务器发出NOOP命令。 这根本不做什么,但足以让IDLE中止并重新发布。

我这里提供了一些代码来执行此操作:

 public void startListening(IMAPFolder imapFolder) { // We need to create a new thread to keep alive the connection Thread t = new Thread( new KeepAliveRunnable(imapFolder), "IdleConnectionKeepAlive" ); t.start(); while (!Thread.interrupted()) { LOGGER.debug("Starting IDLE"); try { imapFolder.idle(); } catch (MessagingException e) { LOGGER.warn("Messaging exception during IDLE", e); throw new RuntimeException(e); } } // Shutdown keep alive thread if (t.isAlive()) { t.interrupt(); } } /** * Runnable used to keep alive the connection to the IMAP server * * @author Juan Martín Sotuyo Dodero  */ private static class KeepAliveRunnable implements Runnable { private static final long KEEP_ALIVE_FREQ = 300000; // 5 minutes private IMAPFolder folder; public KeepAliveRunnable(IMAPFolder folder) { this.folder = folder; } @Override public void run() { while (!Thread.interrupted()) { try { Thread.sleep(KEEP_ALIVE_FREQ); // Perform a NOOP just to keep alive the connection LOGGER.debug("Performing a NOOP to keep alvie the connection"); folder.doCommand(new IMAPFolder.ProtocolCommand() { public Object doCommand(IMAPProtocol p) throws ProtocolException { p.simpleCommand("NOOP", null); return null; } }); } catch (InterruptedException e) { // Ignore, just aborting the thread... } catch (MessagingException e) { // Shouldn't really happen... LOGGER.warn("Unexpected exception while keeping alive the IDLE connection", e); } } } } 

实际上, Java Mail示例包括IMAP IDLE示例,如下所示。 除此之外, IdleManager类可能很有趣。

 /* * Copyright (c) 1996-2010 Oracle and/or its affiliates. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * - Neither the name of Oracle nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ import java.util.*; import java.io.*; import javax.mail.*; import javax.mail.event.*; import javax.activation.*; import com.sun.mail.imap.*; /* Monitors given mailbox for new mail */ public class monitor { public static void main(String argv[]) { if (argv.length != 5) { System.out.println( "Usage: monitor     "); System.exit(1); } System.out.println("\nTesting monitor\n"); try { Properties props = System.getProperties(); // Get a Session object Session session = Session.getInstance(props, null); // session.setDebug(true); // Get a Store object Store store = session.getStore("imap"); // Connect store.connect(argv[0], argv[1], argv[2]); // Open a Folder Folder folder = store.getFolder(argv[3]); if (folder == null || !folder.exists()) { System.out.println("Invalid folder"); System.exit(1); } folder.open(Folder.READ_WRITE); // Add messageCountListener to listen for new messages folder.addMessageCountListener(new MessageCountAdapter() { public void messagesAdded(MessageCountEvent ev) { Message[] msgs = ev.getMessages(); System.out.println("Got " + msgs.length + " new messages"); // Just dump out the new messages for (int i = 0; i < msgs.length; i++) { try { System.out.println("-----"); System.out.println("Message " + msgs[i].getMessageNumber() + ":"); msgs[i].writeTo(System.out); } catch (IOException ioex) { ioex.printStackTrace(); } catch (MessagingException mex) { mex.printStackTrace(); } } } }); // Check mail once in "freq" MILLIseconds int freq = Integer.parseInt(argv[4]); boolean supportsIdle = false; try { if (folder instanceof IMAPFolder) { IMAPFolder f = (IMAPFolder)folder; f.idle(); supportsIdle = true; } } catch (FolderClosedException fex) { throw fex; } catch (MessagingException mex) { supportsIdle = false; } for (;;) { if (supportsIdle && folder instanceof IMAPFolder) { IMAPFolder f = (IMAPFolder)folder; f.idle(); System.out.println("IDLE done"); } else { Thread.sleep(freq); // sleep for freq milliseconds // This is to force the IMAP server to send us // EXISTS notifications. folder.getMessageCount(); } } } catch (Exception ex) { ex.printStackTrace(); } } } 

@ user888307的建议是一个肮脏的黑客,最终失败了。 实际上只有一种正确的方法。

在当前选择的文件夹上调用idle(false)方法。 理想情况下收件箱,因为它将接收所有邮件。

调用idle(false)将基本上挂起线程的运行时,因此最好将idle(false)放在新线程上。 然后,一旦您使用messageCountChange收到新的电子邮件/通知,您必须重新运行此线程。

这是实现这一目标的唯一真正方式。 当我编写一个名为JavaPushMail的程序时,我已经为你的显式问题写了一个包装器。 您可以在我的网站上找到更多信息( http://www.mofirouz.com/wordpress ),或者您可以在GitHub上获取应用程序(目前正在开发中) https://github.com/mofirouz/JavaPushMail

每5分钟检查一次消息计数对我有用:

 new Thread() { @Override public void run() { startTimer(); } private void startTimer() { int seconds = 0; while (true) { try { Thread.sleep(300000); int c = folder.getMessageCount(); } catch (InterruptedException ex) { } catch (MessagingException me) { } } } }.start();