无法在Android粘性服务中保持持久的XMPP连接

3

环境: 为了保证一对一/一对多设备的消息不间断传递,我在后台运行XMPPService作为STICKY服务。 为接收消息,我使用PacketListener,它会在设备接收到数据包时触发。我使用asmack库进行客户端实现,并使用OpenFire作为我的XMPP服务器。

public class XMPPService extends Service implements INetworkReceiver {
private XMPPConnection mXmppConnection;
private XMPPMethods xmpp;
private DatabaseHandler db;
private UserDetails user;
private SmackAndroid smack;
private Registrations registrations;
private static final String TAG="XMPPService";

@Override
public IBinder onBind(final Intent intent) {
    return new LocalBinder<XMPPService>(this);
}

@Override
public void onCreate() {
    super.onCreate();
    registrations=new Registrations(getApplicationContext());
    xmpp=XMPPMethodsImpl.getInstance(getApplicationContext());
    db=DatabaseHandler.getInstance(getApplicationContext());
    if(db!=null){
        user=db.getUserDetails();
    }
    createConnection();     
}

public void createConnection()
{
    ConnectionConfiguration connConfig = new ConnectionConfiguration(
            AppConstants.HOST, AppConstants.PORT);
    connConfig.setSASLAuthenticationEnabled(false);
    connConfig.setDebuggerEnabled(true);
    connConfig.setReconnectionAllowed(true);
    connConfig.setSecurityMode(SecurityMode.disabled);
    mXmppConnection = new XMPPConnection(connConfig);


    Thread thread=new Thread(new Runnable() {
        private Handler handler=new Handler();
        @Override
        public void run() {

            try {
                if(mXmppConnection!=null && !mXmppConnection.isConnected()){
                    mXmppConnection.connect();
                    Log.i("XMPPServiceAsync",
                            "Connected to " + mXmppConnection.getHost());
                    xmpp.setConnection(mXmppConnection);
                }
            } catch (XMPPException e) {
                e.printStackTrace();
            }

            //mXmppConnection.addConnectionListener(new XmppConnectionListener());

            handler.post(new Runnable() {

                @Override
                public void run() {
                    try {
                        if(db!=null && user!=null && mXmppConnection!=null && !mXmppConnection.isAuthenticated()){
                            if(user.getUserName()!=null && !user.getUserName().isEmpty()){
                                mXmppConnection.login(user.getUserName(), "Tp@!");
                                xmpp.setPresence(2, mXmppConnection);
                                xmpp.setConnection(mXmppConnection);
                                Log.i("XMPPServiceAsync",
                                        "Logged in as " + mXmppConnection.getUser());

                            }
                        }
                    }catch (IllegalStateException e) {
                        e.printStackTrace();
                    } catch (XMPPException ex) {
                        ex.printStackTrace();
                    }


                    if(mXmppConnection.isConnected()){
                        xmpp.rejoinGroups();
                        registrations.registerMessageListener(mXmppConnection);
                        registrations.registerPacketListener(mXmppConnection);
                        registrations.registerMultiUserInvitationListener(mXmppConnection, db, user);
                        registrations.registerPingListener(mXmppConnection);
                    }

                }
            });
        }
    });

    if (thread.getState() == Thread.State.NEW )
    { 
        Log.i(TAG,"Thread State: "+ thread.getState()+"");
        thread.start(); 
    } 
}

@Override
public int onStartCommand(final Intent intent, final int flags,
        final int startId) {
    //rejoining rooms
    try{
        Log.v("XMPP Connection before rejoining",mXmppConnection+"");
        if(mXmppConnection.isConnected()){
            registrations.registerMessageListener(mXmppConnection);
            registrations.registerPacketListener(mXmppConnection);
            registrations.registerMultiUserInvitationListener(mXmppConnection, db, user);
            registrations.registerPingListener(mXmppConnection);
        }
    }catch(Exception e){
        e.printStackTrace();
    }

    return Service.START_STICKY;
}


@Override
public boolean onUnbind(final Intent intent) {
    return super.onUnbind(intent);

}


@Override
public void onDestroy() {
    super.onDestroy();

}

@Override
public void reEstablishConnection() {
    createConnection();
}

问题: 当设备空闲一段时间后,设备会从XMPP服务器断开连接,连接变为空值,这违反了XMPP协议的规定,即XMPP服务器会坚持与设备保持持久连接,直到我们强制退出。连接失效的原因不明。当连接失效时,PacketListener停止工作。

我尝试过的所有方法:

  1. 实现了粘性服务以连接服务器和初始化监听器。
  2. 实现了AlarmManager来检查服务是否在后台运行。它还定期检查连接是否存活和通过身份验证。(耗电量很高)
  3. 按照XMPP规范XEP-0199实现Ping管理器。

可以有人来看看吗?


你能解决这个问题吗?我遇到了同样的问题。 - kondal
还没有。你能详细解释一下你遇到了什么问题吗? - Subhendu Pratap Singh
我遇到了同样的问题,我在服务中注册了数据包监听器,但几分钟后就无法监听了。 - kondal
我仍在寻找解决方案。一旦我解决了这个问题,我会分享它。 - Subhendu Pratap Singh
好的,你有什么想法让它持久化吗?我可以试一下。 - kondal
2个回答

1
当设备闲置一段时间后,设备会与XMPP服务器断开连接,连接变为空。
很可能是您的进程被终止了。这是完全正常的(链接)
这违反了XMPP协议的规定,该协议规定XMPP服务器应保持与设备的持久连接,直到我们强制退出登录。
那么没有任何移动设备可以符合XMPP协议规范,因为互联网访问不能保证和普遍存在。此外,用户可以随时摆脱您的进程。
实现了粘性服务以连接服务器和初始化侦听器。
这将导致您的进程和服务在Android终止进程后的某个时间重新启动。在进程被终止和重新启动之间的时间窗口内,您将无法与XMPP服务器通信。

那么像WhatsApp和Telegram这样的应用程序如何能够与其服务器保持持久连接并继续监听设备上到达的任何新数据包呢?我不知道如何实现这样的功能。在服务中初始化连接并向连接添加侦听器是我能想到的唯一方法。 - Subhendu Pratap Singh
@subhendupsingh:我不使用那些应用程序。它们可能正在使用Google Cloud Messaging。它们可能正在使用前台服务。 - CommonsWare
GCM不是实时聊天应用程序的解决方案。我知道我的进程正在被终止,但当它重新启动时,连接和监听器应该再次开始工作,但它们直到我再次启动应用程序才能正常工作。如何在后台保持连接和监听器的活动状态?我不想使用前台服务。 - Subhendu Pratap Singh
1
@subhendupsingh:“连接和监听器应该重新开始工作,但除非我再次启动我的应用程序,否则它们不会工作”--那么您需要找出在Android重新启动您的粘性服务时运行的代码,并将您的连接代码移动到那里。“如何在后台保持我的连接和监听器处于活动状态。我不想使用前台服务”--如果您不打算使用前台服务,也不打算使用GCM,则只能使用您的粘性服务。 - CommonsWare

0

就像commonsware所说的那样。START_STICKY只是意味着当Android需要资源时,它可以自由地终止您的服务。它会在一段时间后重新启动您的服务。但是当这种情况发生时,您需要负责重新建立XMPP连接。


正如您在我发布的代码中所看到的,我已经编写了用于在服务的onCreate中建立连接和初始化监听器的代码。为了进一步测试,我还编写了在服务的onStartCommand中初始化监听器的代码。但是当设备空闲一段时间后,它们都无法工作。我知道START_STICKY的作用,但即使服务被重新启动,根据逻辑,连接也应该重新建立。我只想知道是否这是建立持久连接并始终使监听器工作的正确方法,或者我需要采取其他方法。 - Subhendu Pratap Singh
那我猜测你的 createConnection() 函数不起作用。我快速查看了一下,没有明显的缺陷,但是为此使用线程和处理程序似乎有些绕路。 - Flow
如果在createConnection()中出现问题,那么即使是在应用程序启动时,它也不会工作,但实际上它可以正常运行。我们能否将GCM与xmpp结合使用,因为这样就不需要后台服务了。如果不能,是否有一种方法可以从服务器向设备发送意图,以便每当带有指定意图的数据包到达时广播接收器自动触发。 - Subhendu Pratap Singh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接