Paho MQTT Android服务问题

8

我正在开发一个应用程序,需要使用Paho MQTT Android服务。在测试Paho提供的示例应用程序后,我发现有一些东西需要改变。

https://eclipse.org/paho/clients/android/

应用程序服务似乎在应用程序完全关闭后关闭。如果有更多的消息进来,我想让服务继续运行,即使应用程序关闭。我还在寻找一种方法,在收到新消息时打开特定活动的应用程序。
以下是其中一个回调函数,在消息到达时被调用。我已经尝试实现了一个简单的startActivity来打开一个特定的活动,但如果应用程序关闭/不再运行,则无法工作。
如果有人使用过PAHO MQTT Android服务,请问有没有特定的方法可以防止服务在应用程序关闭时停止,并且如何在消息到达时重新打开应用程序?
    /**
   * @see org.eclipse.paho.client.mqttv3.MqttCallback#messageArrived(java.lang.String,
   *      org.eclipse.paho.client.mqttv3.MqttMessage)
   */
  @Override
  public void messageArrived(String topic, MqttMessage message) throws Exception {

    // Get connection object associated with this object
    Connection c = Connections.getInstance(context).getConnection(clientHandle);

    // create arguments to format message arrived notifcation string
    String[] args = new String[2];
    args[0] = new String(message.getPayload());
    args[1] = topic + ";qos:" + message.getQos() + ";retained:" + message.isRetained();

    // get the string from strings.xml and format
    String messageString = context.getString(R.string.messageRecieved, (Object[]) args);

    // create intent to start activity
    Intent intent = new Intent();
    intent.setClassName(context, "org.eclipse.paho.android.service.sample.ConnectionDetails");
    intent.putExtra("handle", clientHandle);

    // format string args
    Object[] notifyArgs = new String[3];
    notifyArgs[0] = c.getId();
    notifyArgs[1] = new String(message.getPayload());
    notifyArgs[2] = topic;

    // notify the user
    Notify.notifcation(context, context.getString(R.string.notification, notifyArgs), intent,
        R.string.notifyTitle);

    // update client history
    c.addAction(messageString);

    Log.e("Message Arrived", "MESSAGE ARRIVED CALLBACK");

    // used to open the application if it is currently not active
    Intent i = new Intent(context, ConnectionDetails.class);
    i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    i.putExtra("handle", clientHandle);
    context.startActivity(i);


  }

1
你有没有解决这个问题的机会? - ᴘᴀɴᴀʏɪᴏᴛɪs
很遗憾,我没有。我已经开始使用另一个示例进行工作了。如果我找到解决方案,我会发布它。 - Coova
你现在解决了这个问题吗?或者至少找到了其他可行的(令人满意的)示例吗? - Muhamed Huseinbašić
我有同样的问题。 - Jdruwe
我也有同样的问题。 - lawonga
4个回答

7
尽管这似乎不是问题的完整解决方案,但我将发布我的解决方法,以帮助其他人。

对我来说,问题始于用户从最近使用的应用程序列表中滑出该应用程序。如此处所述,这种操作不仅会终止活动,而且会终止整个进程,包括MqttService。然后,正如在Android线程中提到的那样,Android会识别您的服务应该重新启动,并安排重新启动已终止的服务。但是,这并不意味着连接恢复,因为所有连接都绑定到活动。

因此,除非您找到一种消除服务停止问题的方法,否则当用户决定滑出应用程序时,您保证会失去与代理的连接。

然而,这并不是世界末日,因为我们可以在失去连接后简单地重新连接。问题是,这次我们没有活动来执行所需的操作。您必须修改Paho Android服务库的源代码,或者更简单的方法是创建另一个服务。

所有的连接都将在这个新服务中进行,任何想要连接的活动都应该与这个服务进行通信。这样做的好处是我们可以让服务粘性,并且即使用户刷掉我们的应用程序并杀死它,它也会立即重新启动,我们只需要重新连接就可以恢复。因此,通过这个非常简单的服务进行演示:
public class MessagingService extends Service {
    private static final String TAG = "MessagingService";
    private MqttAndroidClient mqttClient;
    String deviceId;



    @Override
    public void onCreate() {
    }
    private void setClientID() {
        WifiManager wifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
        WifiInfo wInfo = wifiManager.getConnectionInfo();
        deviceId = wInfo.getMacAddress();
        if (deviceId == null) {
            deviceId = MqttAsyncClient.generateClientId();
        }
    }

    public class MsgBinder extends Binder {
        public MsgServ getService() {
            return MsgServ.this;
        }
    }

    public void doConnect(){
        // Using some of the Paho sample app classes
        String server = ConfigClass.BROKER_URI;
        MemoryPersistence mem = new MemoryPersistence();
        mqttClient = new MqttAndroidClient(this,ConfigClass.BROKER_URI,deviceId,mem);
        MqttConnectOptions conOpt = new MqttConnectOptions();
        String clientHandle = server + deviceId;
        Connection con = new Connection(clientHandle, deviceId, ConfigClass.BROKER_ADDRESS,
                                        ConfigClass.BROKER_PORT, this, mqttClient, false);
        conOpt.setCleanSession(false);
        conOpt.setConnectionTimeout(ConfigClass.CONN_TIMEOUT);
        conOpt.setKeepAliveInterval(ConfigClass.CONN_KEEPALIVE);
        conOpt.setUserName("testclient");
        conOpt.setPassword("password".toCharArray());
        String[] actionArgs = new String[1];
        actionArgs[0] = deviceId;
        final ActionListener callback =
                new ActionListener(this, ActionListener.Action.CONNECT, clientHandle,
                                   actionArgs);
        mqttClient.setCallback(new MqttCallbackHandler(this, clientHandle));
        mqttClient.setTraceCallback(new MqttTraceCallback());
        con.addConnectionOptions(conOpt);
        Connections.getInstance(this).addConnection(con);
        try {
            mqttClient.connect(conOpt, null, callback);
            Log.d("Con", "Connected");
        } catch (MqttException e) {
            Log.d("Con", "Connection failed");
            e.printStackTrace();
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        doConnect();
        return START_STICKY;
    }

}

服务器日志:

1433455371: New client connected from 192.168.2.5 as ed:0a:2b:56:b5:45 (c0, k30, u'testclient').
1433455371: Sending CONNACK to ed:0a:2b:56:b5:45 (1, 0)
1433455375: Socket error on client ed:0a:2b:56:b5:45, disconnecting.
1433455377: New connection from 192.168.2.5 on port 1883.
1433455377: Client ed:0a:2b:56:b5:45 disconnected.
1433455377: New client connected from 192.168.2.5 as ed:0a:2b:56:b5:45 (c0, k30, u'testclient').
1433455377: Sending CONNACK to ed:0a:2b:56:b5:45 (1, 0)

当我关闭应用程序并且服务被终止时,您可以看到它会重新启动并重新连接,并在此之后保持活动状态。从这里开始,您应该能够完成其余的工作。也许可以使用新收到的消息创建一个通知,该通知将打开应用程序。只需记住在新创建的服务中执行所有操作,该服务保证维护连接。

您介意分享一个完全可工作的示例(例如在Github上)吗?我尝试让它工作遇到了很多麻烦。 - Jdruwe
谢谢,我已经使用这个设计来解决失去连接/订阅的问题,但现在在应用程序活动中,我需要与服务交互以发布内容,我该怎么做? - Japheth Ongeri - inkalimeva
@inkalimeva 请参考如何让Android Service与Activity通信 - ᴘᴀɴᴀʏɪᴏᴛɪs

5
如果您使用任务管理器关闭应用程序,我认为这是不可能的,因为“完全关闭”应用程序也将停止其包含的任何服务。即使服务是“粘性启动”的,它也不会在我的设备上重新启动。如果您通过最近任务中的滑动方式关闭应用程序,则服务仍然在运行。有关更多信息,请参见此处:Killing android application from task manager kills services started by app 但是,我认为另一个问题是,即使服务仍在运行,应用程序也包含由服务调用的回调对象。如果应用程序不再运行,则回调对象不存在,因此永远不会被调用。
以下是我实现此功能的高级视图。这已经在生产中运行了几个月,但不幸的是,我不拥有该代码,无法发布它。
  • 我创建了一个单例对象,托管MQTTService/mqttAndroidClient。这公开了连接/断开连接的公共方法,并包含用于接收消息的MqttCallback对象。它还处理所需的连接丢失和重试机制。这是最棘手的部分,但我不能在此发布它。
  • 我创建了一个Application对象,在onCreate()中进行连接并在onTerminate()中关闭连接
  • 我注册了一个BroadcastReceiver,它获取驻留在Application对象中的BOOT_COMPLETED操作,它具有空实现,但它会启动应用程序,以便mqtt服务在引导时连接。
这消除了需要运行任何给定活动来接收消息的需要。它似乎也能够抵御关闭应用程序的情况,唯一的例外是如果您在应用程序设置中“强制关闭”它。这是有道理的,因为用户明确选择关闭它。

我对这个解决方案很感兴趣。但是你说:“如果你通过最近任务将应用程序滑动关闭,服务确实仍在运行。” 我不认为它会继续运行(至少不是应该的方式)。之后不会收到任何消息(没有通知,没有任何东西)。如果重新启动应用程序,则发送到这些主题的消息将丢失。 :( - Muhamed Huseinbašić
1
我更新了我的答案并添加了更多细节。很抱歉我不能发布实际的代码,但我希望这能让你了解所涉及的内容。 - Hugh Jeffner

2
我知道这是对于这个问题的一个晚回答,但我想分享一下我所做的事情,希望能帮到某些人。
我创建了自己的服务(Service),以管理与代理的连接,并始终保持每个Android设备的单个连接实例。
重申解决方案的特点:

此解决方案的主要特点:

  1. 服务(Service)在存活期间保持单个实例。
  2. 如果服务(Service)被杀死,Android会重新启动它(因为START_STICKY)。
  3. 设备启动时可以启动服务(Service)。
  4. 服务(Service)在后台运行并始终连接以接收通知。
  5. 如果服务(Service)仍然存活,则再次调用startService(..)将触发其onStartCommand()。在此方法中,我们只需检查此客户端是否连接到代理并根据需要进行连接/重新连接即可。
请查看此处以获取完整详细的答案。

1
我认为Eclipse Paho提供了你所需的一切。我可以轻松地启动我的应用程序并运行服务。更多细节请查看我在Paho MQTT Android service wake up activity中的回答。
我希望这能帮助到你。

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