javamail idle stops triggering messagesAdded after a while,线程锁定

javamail idle stops triggering messagesAdded after a while, thread locked

我正在开发一个 android 应用程序来接收和处理邮件消息。该应用程序必须连接到 IMAP 服务器并保持连接有效,以便它可以立即查看和处理新邮件消息(邮件包含来自邮件 api 服务器的 json 数据)。该应用程序有两种模式,手动和实时连接。这是我的一些代码:

class Idler {
Thread th;
volatile Boolean isIdling=false;
boolean shouldsync=false;//we need to see if we have unseen mails
Object idleLock;
Handler handler=new Handler();
IMAPFolder inbox;
public boolean keppAliveConnection;//keep alive connection, or manual mode

//This thread should keep the idle connection alive, or in case it's set to manual mode (keppAliveConnection=false) get new mail.
Thread refreshThread;
synchronized void refresh()
{
    if(isIdling)//if already idling, just keep connection alive
    {
        refreshThread =new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    inbox.doCommand(new IMAPFolder.ProtocolCommand() {
                        @Override
                        public Object doCommand(IMAPProtocol protocol) throws ProtocolException {
                            //Why not noop?
                            //any call to IMAPFolder.doCommand() will trigger waitIfIdle, this
                            //issues a "DONE" command and waits for idle to return(ideally with a DONE server response).
                            // So... I think NOOP is unnecessary
                            //protocol.simpleCommand("NOOP",null); I'm not issuing noop due to what I said ^

                            //PD: if connection was broken, then server response will never arrive, and idle will keep running forever
                            //without triggering messagesAdded event any more :'( I see any other explanation to this phenomenon


                            return null;
                        }
                    });
                } catch (MessagingException e) {
                    e.printStackTrace();
                }
            }
        },"SyncThread");
        refreshThread.start();
    }
    else
    {
        getNewMail();//If manual mode keppAliveConnection=false) get the new mail
    }
}
public Idler()
{
    th=new Thread(new Runnable() {

        @SuppressWarnings("InfiniteLoopStatement")
        @Override
        public void run() {
            while (true)
            {
                try {
                    if(refreshThread !=null && refreshThread.isAlive())
                        refreshThread.interrupt();//if the refresher thread is active: interrupt. I thing this is not necessary at this point, but not shure
                    initIMAP();//initializes imap store
                    try {
                        shouldsync=connectIMAP()||shouldsync;//if was disconnected or ordered to sync: needs to sync
                    }
                    catch (Exception e)
                    {
                        Thread.sleep(5000);//if can't connect: wait some time and throw
                        throw e;
                    }
                    shouldsync=initInbox()||shouldsync;//if inbox was null or closed: needs to sync
                    if(shouldsync)//if needs to sync
                    {
                        getNewMail();//gets new unseen mail
                        shouldsync=false;//already refreshed, clear sync "flag"
                    }

                    while (keppAliveConnection) {//if sould keep idling "forever"
                        synchronized (idleLock){}//MessageCountListener may be doing some work... wait for it
                        isIdling = true; //set isIdling "flag"
                        handler.removeCallbacksAndMessages(null);//clears refresh scheduled tasks
                        handler.postDelayed(new Runnable() {
                            @Override
                            public void run() {
                                refresh();
                            }
                        },1200000);//Schedule a refresh in 20 minutes
                        inbox.idle();//start idling
                        if(refreshThread !=null && refreshThread.isAlive())
                            refreshThread.interrupt();//if the refresher thread is active: interrupt. I thing this is not necessary at this point, but not shure
                        handler.removeCallbacksAndMessages(null);//clears refresh scheduled tasks
                        isIdling=false;//clear isIdling "flag"
                        if(shouldsync)
                            break;//if ordered to sync... break. The loop will handle it upstairs.
                        synchronized (idleLock){}//MessageCountListener may be doing some work... wait for it

                    }
                }
                catch (Exception e) {
                    //if the refresher thread is active: interrupt
                    //Why interrupt? refresher thread may be waiting for idle to return after "DONE" command, but if folder was closed and throws
                    //a FolderClosedException, then it could wait forever...., so... interrupt.
                    if (refreshThread != null && refreshThread.isAlive())
                        refreshThread.interrupt();
                    handler.removeCallbacksAndMessages(null);//clears refresh scheduled tasks
                }
            }
        }
    },"IdlerThread");
    th.start();
}

private synchronized void getNewMail()
{
    shouldsync=false;
    long uid=getLastSeen();//get last unprocessed mail
    SearchTerm searchTerm=new UidTerm(uid,Long.MAX_VALUE);//search from las processed message to the las one.
    IMAPSearchOperation so=new IMAPSearchOperation(searchTerm);
    try {
        so.run();//search new messages
        final long[] is=so.uids();//get unprocessed messages count
        if (is.length > 0) {//if some...
            try {
                //there are new messages
                IMAPFetchMessagesOperation fop=new IMAPFetchMessagesOperation(is);
                fop.run();//fetch new messages
                if(fop.messages.length>0)
                {
                    //process fetched messages (internally sets the last seen uid value & delete some...)
                    processMessages(fop.messages);
                }
                inbox.expunge();//expunge deleted messages if any
            }
            catch (Exception e)
            {
                //Do something
            }
        }
        else
        {
            //Do something
        }
    }
    catch (Exception e)
    {
        //Do something
    }
}


private synchronized void initIMAP()
{
    if(store==null)
    {
        store=new IMAPStore(mailSession,new URLName("imap",p.IMAPServer,p.IMAPPort,null,p.IMAPUser,p.IMAPPassword));
    }
}

private boolean connectIMAP() throws MessagingException {
    try {
        store.connect(p.IMAPServer, p.IMAPPort, p.IMAPUser, p.IMAPPassword);
        return true;
    }
    catch (IllegalStateException e)
    {
        return false;
    }
}

//returns true if the folder was closed or null
private synchronized boolean initInbox() throws MessagingException {
    boolean retVal=false;
    if(inbox==null)
    {//if null, create. This is called after initializing store
        inbox = (IMAPFolder) store.getFolder("INBOX");
        inbox.addMessageCountListener(countListener);
        retVal=true;//was created
    }
    if(!inbox.isOpen())
    {
        inbox.open(Folder.READ_WRITE);
        retVal=true;//was oppened
    }
    return retVal;
}

private MessageCountListener countListener= new MessageCountAdapter() {
    @Override
    public void messagesAdded(MessageCountEvent ev) {
        synchronized (idleLock)
        {
            try {
                processMessages(ev.getMessages());//process the new messages, (internally sets the last seen uid value & delete some...)
                inbox.expunge();//expunge deleted messajes if any
            } catch (MessagingException e) {
                //Do something
            }

        }
    }
};

}

问题是:有时,当用户刷新或应用程序自动刷新时,在实时连接模式下,这两种情况中的一种或两种都会使我的应用程序无法接收新消息。这是来自javamail源代码。

1: IdlerThread 进入监听状态:

//I don't know why sometimes it enters monitor state here.
private synchronized void throwClosedException(ConnectionException cex) 
        throws FolderClosedException, StoreClosedException {
// If it's the folder's protocol object, throw a FolderClosedException;
// otherwise, throw a StoreClosedException.
// If a command has failed because the connection is closed,
// the folder will have already been forced closed by the
// time we get here and our protocol object will have been
// released, so if we no longer have a protocol object we base
// this decision on whether we *think* the folder is open.
if ((protocol != null && cex.getProtocol() == protocol) ||
    (protocol == null && !reallyClosed))
        throw new FolderClosedException(this, cex.getMessage());
    else
        throw new StoreClosedException(store, cex.getMessage());
}

2:"refresherThread"进入等待状态:

  void waitIfIdle() throws ProtocolException {
assert Thread.holdsLock(messageCacheLock);
while (idleState != RUNNING) {
    if (idleState == IDLE) {
    protocol.idleAbort();
    idleState = ABORTING;
    }
    try {
    // give up lock and wait to be not idle
    messageCacheLock.wait();//<-----This is the line is driving me crazy.
    } catch (InterruptedException ex) { }
}
}

作为这两个线程之一 "stops" 运行(等待和监视状态)我的应用程序在达到此条件时无用。在我的国家,移动数据网络非常不稳定、缓慢且昂贵(GSM)所以它必须具有故障恢复能力并注意每一个传输位。

我想当连接无声地失败并且 refresherThread 开始执行它的工作时就会出现问题。如果 idle 处于活动状态,它会发出 DONE 命令,但是,随着连接断开,当 idle 尝试抛出 FolderClosedException 时,一个或两个线程将被无限期锁定。

所以,我的问题是:为什么会出现这种情况,如何预防呢?我怎样才能安全地保持空闲循环运行而不被锁定?

我试了很多东西,直到筋疲力尽都没有结果。

以下是我阅读过的一些主题,但没有找到解决我的问题的方法。在我的国家/地区,互联网也非常昂贵,因此我无法随心所欲地进行研究,也无法列出我访问过的所有网址来寻找信息。

JavaMail: Keeping IMAPFolder.idle() alive

JavaMail: Keeping IMAPFolder.idle() alive

Javamail : Proper way to issue idle() for IMAPFolder

请原谅我的英语。任何建议将不胜感激。我听说过这个网站的严格性,所以请保持温和,我是新来的。

请务必设置 timeout properties 以确保您不会在等待死连接或服务器时挂起。

您应该调用Folder.isOpen 或Folder.getMessageCount,而不是直接发出nop 命令;如果需要,他们会发出 nop 命令。

如果文件夹被异步关闭 (FolderClosedException),您将需要重新启动空闲循环。