Android XMPP 避免资源冲突

标签 android xmpp smack

我正在为我的 XMPP 连接/服务器使用 Smack 和 Openfire。但是我遇到了资源冲突这个非常(显然)常见的问题。谁能告诉我处理冲突的正确方法?

Openfire 设置为始终踢掉原始资源(这是一个定制平台,不向公众开放)。但是我仍然收到错误并且没有获得新的连接。我的 XMPP 类(class)如下。

package com.goosesys.gaggle.services;

import java.util.Collection;

import org.jivesoftware.smack.Chat;
import org.jivesoftware.smack.ChatManagerListener;
import org.jivesoftware.smack.Connection;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.ConnectionListener;
import org.jivesoftware.smack.Roster;
import org.jivesoftware.smack.Roster.SubscriptionMode;
import org.jivesoftware.smack.RosterListener;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.packet.Presence.Type;

import com.google.gson.Gson;
import com.goosesys.gaggle.Globals;
import com.goosesys.gaggle.application.AppSettings;
import com.goosesys.gaggle.application.Utility;

import android.app.Service;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.util.Log;
import android.widget.Toast;

public class BackgroundXmppConnector extends Service
{
    private ConnectionConfiguration acc;
    private XMPPConnection xConnection;
    private final IBinder mBinder = new XmppBinder();
    private final Handler mHandler = new Handler();

    private final int manualReconnectionTimer = (5 * 60 * 1000);

    private static int mInterval1m = (2 * 60 * 1000);
    private static int mInterval5m = (5 * 60 * 1000);
    private static boolean bConnecting = false;
    private static final Object connectLock = new Object(); 
    private static final Object checkLock = new Object();

    private final Runnable checkConnection = new Runnable()
    {
        @Override
        public void run()
        {
            synchronized(checkLock)
            {
                Log.d("BXC", "Handler running - Checking connection");
                checkConnectionStatus();
            }
        }
    };

    private final Runnable killConnection = new Runnable()
    {
        @Override
        public void run()
        {
            synchronized(checkLock)
            {
                Log.d("BXC", "Killing connection and restarting");

                // Manually disconnect and restart the connection every 5 minutes
                if(xConnection != null)
                    xConnection.disconnect();

                destroyConnectionAndRestart();
                new LoginTask().execute();

                mHandler.postDelayed(this, mInterval5m);
            }
        }
    };

    @Override
    public void onCreate()
    {   
        Log.i("BXC", "BackgroundXmppConnector Service has been created");

        // Checks the connection state every 1 minute //
        mHandler.postDelayed(checkConnection, mInterval1m);
        mHandler.postDelayed(killConnection, mInterval5m);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId)
    {   
        Log.d("BXC", "Xmpp Connector Started"); 
        new LoginTask().execute();

        return Service.START_STICKY;
    }

    private void destroyConnectionAndRestart()
    {
        xConnection.disconnect();
        xConnection = null;     
        Globals.backgroundXmppConnectorRunning = false;
        bConnecting = false;
    }

    private void setupConnection()
    {   
        Log.d("BXC", "Settting up XMPP connection");
        try 
        {
            if(!bConnecting && !Globals.backgroundXmppConnectorRunning)
            {
                acc = new ConnectionConfiguration(AppSettings.XMPP_SERVER_HOST,
                        AppSettings.XMPP_SERVER_PORT);
                acc.setSecurityMode(SecurityMode.disabled);
                acc.setSASLAuthenticationEnabled(false);
                acc.setReconnectionAllowed(false);
                acc.setSendPresence(true);

                xConnection = new XMPPConnection(acc);          
                xConnection.addConnectionListener(new ConnectionListener()
                    {
                        @Override
                        public void connectionClosed() 
                        {
                            Log.e("BXC", "Xmpp connection closed");
                            Globals.backgroundXmppConnectorRunning = false;
                            Globals.numberOfDisconnects += 1;
                            //destroyConnectionAndRestart();

                            Utility.writeToLog(getApplicationContext(), "Xmpp Connection closed - disconnected# (" + Globals.numberOfDisconnects + ")");
                        }

                        @Override
                        public void connectionClosedOnError(Exception e) 
                        {
                            Log.e("BXC", "Xmpp connection closed with error: " + e);
                            Globals.backgroundXmppConnectorRunning = false; 
                            Globals.numberOfDisconnectsOnError += 1;                            
                            // This is more than likely due to a conflict loop - it's best to disconnect and nullify
                            // our connection and let the software restart when it checks every 5 minutes
                            if(e.toString().toUpperCase().contains("CONFLICT"))
                            {
                                Log.e("BXC", "Conflict connection loop detected - Waiting");
                            }

                            Utility.writeToLog(getApplicationContext(), "Xmpp Connection closed with error [" + e + "] - disconnected# (" + Globals.numberOfDisconnectsOnError + ")");
                        }

                        @Override
                        public void reconnectingIn(int seconds) 
                        {
                            Log.i("BXC", "Xmpp connection, reconnecting in " + seconds + " seconds");
                            Globals.backgroundXmppConnectorRunning = false;                 
                            bConnecting = true;
                        }

                        @Override
                        public void reconnectionFailed(Exception e) 
                        {
                            Log.e("BXC", "Xmpp reconnection failed: " + e);
                            Globals.backgroundXmppConnectorRunning = false;     
                            //destroyConnectionAndRestart();

                            Utility.writeToLog(getApplicationContext(), "Xmpp reConnection failed with error [" + e + "] - disconnected# (" + Globals.numberOfDisconnects + ")");
                        }

                        @Override
                        public void reconnectionSuccessful() 
                        {
                            Log.i("BXC", "Xmpp reconnected successfully");
                            Globals.backgroundXmppConnectorRunning = true;
                            bConnecting = false;
                        }           
                });
            }
            else
            {
                Log.i("BXC", "Already in connecting state");
            }

        } 
        catch (Exception e)
        {
            Log.e("BXC", e.getMessage());
        }   
    }

    public boolean sendMessage(Intent intent)
    {
        if(xConnection != null && xConnection.isConnected())
        {
            String jsonObject;
            Bundle extras = intent.getExtras();
            if(extras != null)
            {
                jsonObject = extras.getString("MESSAGEDATA");
                Message m = new Gson().fromJson(jsonObject, Message.class);
                if(m != null)
                {
                    sendMessage(m);
                }
                else
                {
                    Log.e("BXC", "Message to send was/is null. Can't send.");
                }

                m = null;
                jsonObject = null;
                extras = null;
            }

            Log.i("BXC", "Sending Xmpp Packet");
            return true;
        }

        return false;
    }

    /*
     * Sends message to xmpp server - message packet in form of
     * 
     * --------------------MESSAGE PACKET-------------------------
     * TO
     * -----------------------
     * FROM
     * -----------------------
     * BODY
     *      TRANSACTION-------------------------------------------
     *          MessageType
     *          --------------------------------------------------
     *          TransactionObject
     */
    private void sendMessage(Message m)
    {
        try
        {
            Log.d("BXC", "Sending transaction message to Xmpp Server");
            xConnection.sendPacket(m);
            //Toast.makeText(getApplicationContext(), "Packet sent to XMPP", Toast.LENGTH_LONG).show();
        }
        catch(Exception ex)
        {
            ex.printStackTrace();
        }
    }


    private void checkConnectionStatus()
    {
        Log.d("BXC", "Checking Xmpp connection status");

        if(xConnection == null || xConnection.isAuthenticated() == false ||
                xConnection.isConnected() == false || xConnection.isSocketClosed() ||
                Globals.backgroundXmppConnectorRunning == false)
        {
            Log.e("BXC", "Connection to server is dead. Retrying");
            Toast.makeText(getApplicationContext(), "Connection dead - retrying", Toast.LENGTH_SHORT).show();
            destroyConnectionAndRestart();
            new LoginTask().execute();
        }
        else
        {
            Log.i("BXC", "Connection appears to be valid");
            Toast.makeText(getApplicationContext(), "Connection valid", Toast.LENGTH_SHORT).show();
        }

    }

    // BINDER ////////////////////////////////////////////////////////////////////////////////
    @Override
    public IBinder onBind(Intent intent) 
    {
        return mBinder;
    }


    // INTERNAL CLASSES //////////////////////////////////////////////////////////////////////
    public class XmppBinder extends Binder
    {
        public BackgroundXmppConnector getService(){
            return BackgroundXmppConnector.this;
        }
    }

    private class LoginTask extends AsyncTask<Void, Void, Void>
    {
        @Override
        protected Void doInBackground(Void... params)
        {
            // First ensure we've got a connection to work with first
            if(Utility.hasActiveInternetConnection(getApplicationContext()) && 
                    ((!bConnecting) || (!Globals.backgroundXmppConnectorRunning)))
            {
                try
                {   
                    //bConnecting = true;
                    Log.d("BXC", "Beginning connection");
                    synchronized(connectLock)
                    {
                        setupConnection();                              
                        xConnection.connect();  
                        Log.i("BXC", "Login credentials: " + Utility.getAndroidID(getApplicationContext()) + " " + AppSettings.XMPP_KEYSTORE_PASSWORD);
                        xConnection.login(Utility.getAndroidID(getApplicationContext()), AppSettings.XMPP_KEYSTORE_PASSWORD);                   

                        xConnection.getChatManager().addChatListener(new ChatManagerListener(){
                            @Override
                            public void chatCreated(final Chat chat, boolean createdLocally)
                            {
                                if(!createdLocally)
                                {
                                    // add chat listener //
                                    chat.addMessageListener(new BackgroundMessageListener(getApplicationContext()));
                                }
                            }

                        });                 

                        Presence p = new Presence(Presence.Type.subscribe);
                        p.setStatus("Out and About");
                        xConnection.sendPacket(p);

                        Roster r = xConnection.getRoster();                 
                        r.setSubscriptionMode(SubscriptionMode.accept_all);
                        r.createEntry(AppSettings.BOT_NAME, "AbleBot", null);
                        r.addRosterListener(new RosterListener(){

                            @Override
                            public void entriesAdded(Collection<String> addresses) 
                            {               
                                for(String s : addresses)
                                {
                                    Log.d("BXC", "Entries Added: " + s);
                                }
                            }

                            @Override
                            public void entriesDeleted(Collection<String> addresses) 
                            {
                                for(String s : addresses)
                                {
                                    Log.d("BXC", "Entries Deleted: " + s);
                                }                           
                            }

                            @Override
                            public void entriesUpdated(Collection<String> addresses) 
                            {   
                                for(String s : addresses)
                                {
                                    Log.d("BXC", "Entries updated: " + s);
                                }                           
                            }

                            @Override
                            public void presenceChanged(Presence presence) 
                            {   
                                Log.d("BXC", "PresenceChanged: " + presence.getFrom());
                            }                       
                        });
                    }           
                }
                catch(IllegalStateException ex)
                {
                    Log.e("BXC", "IllegalStateException -->");
                    if(ex.getMessage().contains("Already logged in to server"))
                    {
                        Globals.backgroundXmppConnectorRunning = true;                      
                    }
                    else
                    {
                        Globals.backgroundXmppConnectorRunning = false;
                        Utility.writeExceptionToLog(getApplicationContext(), ex);

                        ex.printStackTrace();
                    }
                }
                catch(XMPPException ex)
                {
                    Log.e("BXC", "XMPPException -->");
                    Globals.backgroundXmppConnectorRunning = false;
                    Utility.writeExceptionToLog(getApplicationContext(), ex);
                    ex.printStackTrace();
                }
                catch(NullPointerException ex)
                {
                    Log.e("BXC", "NullPointerException -->");
                    Globals.backgroundXmppConnectorRunning = false;
                    Utility.writeExceptionToLog(getApplicationContext(), ex);
                    ex.printStackTrace();
                }
                catch(Exception ex)
                {
                    Log.e("BXC", "Exception -->");
                    Globals.backgroundXmppConnectorRunning = false;
                    Utility.writeToLog(getApplicationContext(), ex.toString());
                    ex.printStackTrace();
                }

                return null;    
            }
            else
            {
                Log.i("BXC", "No active internet data connection - will retry");
            }

            return null;
        }

        @Override
        protected void onPostExecute(Void ignored)
        {
            if(xConnection != null)
            {
                if(xConnection.isConnected() && (!xConnection.isSocketClosed()))
                {
                    Log.i("BXC", "Logged in to XMPP Server");
                    Globals.backgroundXmppConnectorRunning = true;

                    mHandler.postDelayed(checkConnection, mInterval1m);
                }
                else
                {
                    Log.e("BXC", "Unable to log into XMPP Server.");    
                    Globals.backgroundXmppConnectorRunning = false;

                    destroyConnectionAndRestart();
                }
            }
            else
            {
                Log.e("BXC", "Xmpp Connection object is null");
                Globals.backgroundXmppConnectorRunning = false;
            }
        }
    }

}

据我所知,openfire(当设置为始终启动时)将始终启动原始资源,然后允许新登录连接,但我根本看不到这一点。任何帮助是极大的赞赏。谢谢。

最佳答案

处理资源冲突的正确方法是除非万不得已,否则不要使用固定资源。如果您的客户端在登录时未指定任何资源,则服务器应为其分配一个随机生成的永远不会冲突的资源。

您需要指定固定资源的原因很少,因为大多数客户端无论如何都会隐藏您的联系人资源,还有其他原因可以说明为什么在每个连接上都有一个新资源是有利的(比如避免群聊不同步的常见错误,因为群聊的服务器没有意识到连接用户实际上是一个新 session )。

固定资源的一个大问题是重新连接循环,如果服务器配置为踢掉旧的冲突资源,两个客户端会反复踢对方。您应该确保在收到资源冲突错误时不会自动重新连接。

关于Android XMPP 避免资源冲突,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23605562/

相关文章:

java - 如何在openfire中获取 "both"订阅类型

android - asmack 中的 ReconnectionManager

java - packetFilter和accept有什么区别?

android - 手机进入空闲状态时后台服务停止

android - 如何正确使用资源收缩来减小APK大小?

android - 如何更改android studio中的默认主题?

ios - XMPPFramework - 自动接受状态订阅请求

iOS:XMPP:群聊消息的消息归档

ios - 如何使用XMPP框架检索成员聊天室列表?

java - Android 对话框和异步任务