如何像前台服务一样启动 socket.io 以保持连接并监听消息

Posted

技术标签:

【中文标题】如何像前台服务一样启动 socket.io 以保持连接并监听消息【英文标题】:How to start socket.io like foreground service to stay connected and listen for messages 【发布时间】:2019-09-26 05:19:30 【问题描述】:

我正在使用此源应用程序与其他设备聊天。但是如何让它像服务一样启动,这样我就可以启动前台服务。 我需要在 Service 中重写 MainFragment 和 LoginActivity 吗?

socket.io 应用socket.io-android-chat

我在 SocketService 类中尝试过类似的方法,即使应用程序关闭,我还需要在 Service for App 中包含其他内容以获取通知消息。

public class SocketService extends Service 
    private Socket mSocket;
    public static final String TAG = SocketService.class.getSimpleName();
    private static final String NOTIFICATION_CHANNEL_ID_DEFAULT = "App running in background";
    String GROUP_KEY_WORK_EMAIL = "com.android.example.WORK_EMAIL";
    @Override
    public IBinder onBind(Intent intent) 
        // TODO: Return the communication channel to the service.
        throw null;
    

    @Override
    public void onCreate() 
        super.onCreate();
        Toast.makeText(this, "on created", Toast.LENGTH_SHORT).show();

        NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
                .setGroup(GROUP_KEY_WORK_EMAIL);
        Notification notification = builder.build();
        NotificationCompat.BigTextStyle bigTextStyle = new NotificationCompat.BigTextStyle();
        // Set big text style.
        builder.setStyle(bigTextStyle);
        startForeground(3, notification);
    

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) 
        Toast.makeText(this, "start command", Toast.LENGTH_SHORT).show();
        try 
            mSocket = IO.socket(Constants.CHAT_SERVER_URL);
         catch (URISyntaxException e) 
            throw new RuntimeException(e);
        
        mSocket.on("newMessageReceived", onNewMessage);
        mSocket.connect();
        return START_STICKY;
    

    private Emitter.Listener onNewMessage = new Emitter.Listener() 
        @Override
        public void call(Object... args) 
            JSONObject data = (JSONObject) args[0];
            String username;
            String message;
            try 
                username = data.getString("username");
                message = data.getString("message");
             catch (JSONException e) 
                Log.e(TAG, e.getMessage());
                return;
            

            Log.d(TAG, "call: new message ");
            setNotificationMessage(message, username);
        
    ;

    public void setNotificationMessage(CharSequence message, CharSequence title) 
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
        builder.setSmallIcon(R.drawable.ic_launcher);
        builder.setContentTitle(title);
        builder.setContentText(message);
        NotificationManagerCompat nm = NotificationManagerCompat.from(this);
        nm.notify(3, builder.build());
    


【问题讨论】:

你有解决办法吗? 【参考方案1】:

当您的应用在后台时,您不应使用前台服务来获取通知消息

你应该使用firebase push notification

但是,如果您仍然需要在前台服务中使用套接字连接

只需简单地创建一个单例类来处理所有套接字连接并将其用于前台服务,如下所示

 public class SocketManger 


        private  static SocketManger socketManger;

        Socket socket;
        Callback<Boolean> onConnect;

        public void init(Callback<Boolean> onConnect)
            this.onConnect = onConnect;
            connectToSocket();
            listenToPublicEvents();
        

        private void connectToSocket()
            try
                IO.Options opts = new IO.Options();
                //optional parameter for authentication
                opts.query = "token=" + YOUR_TOKEN;
                opts.forceNew = true;
                opts.reconnection = true;
                opts.reconnectionDelay = 1000;
                socket = IO.socket(YOUR_URL, opts);
                socket.connect();

            
            catch(URISyntaxException e)

                throw new RuntimeException(e);
            
        

        private void listenToPublicEvents()
            socket.on(Socket.EVENT_CONNECT, args -> 
                if(onConnect!=null)
                    onConnect.onResult(true);
                     );

            socket.on(Socket.EVENT_DISCONNECT, args ->
                    if(onConnect!=null)
                    onConnect.onResult(false);
                   );
        


        public void emit(String event, JSONObject data, Ack ack)
            socket.emit(event, new JSONObject[]data, ack);
        


        public void on(String event, Emitter.Listener em)
            socket.on(event, em);
        


        public static SocketManger getSocketManger() 
            if(socketManger == null)
                socketManger = new SocketManger();
            
            return socketManger;
        


        public boolean isConnected()
            return socket!=null && socket.connected();
        

        public void onDestroy() 
            onConnect = null;
            socket.disconnect();
        

    public interface Callback<T> 
      void onResult(T t);
     

    

并将此代码添加到您的前台服务

SocketManager socketManger = SocketManger.getSocketManger();

    @Override
    public void onCreate() 
      socketManger.init(this::onSocketEvent);
   

 public void onSocketEvent(boolean connect)
    //your code when the socket connection connect or disconnect
    

并确保在服务被销毁时断开套接字

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

【讨论】:

对于即时消息,我们无法中继 firebase 通知。其中一些没有交付。正如我之前搜索过的后台服务,工作时间表,警报管理器,工作管理器不适用于即时消息。【参考方案2】:

如果您希望在应用程序启动后立即启动服务,您可以从 onCreate 方法中的自定义应用程序类启动它。

或者您可以从任何 Activity 启动它,例如。如果您想从某个活动启动服务,则从 onCreate 方法开始。

或者您可以在设备启动时从 BroadcastReceiver 开始。在这种情况下使用 BOOT_COMPLETED 操作:

要启动您的服务,只需在您想启动服务的任何地方使用此代码:

Intent intent = new Intent(context, SocketService.class);


if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) 
    context.startForegroundService(intent);
 else 
    context.startService(intent);

【讨论】:

以上是关于如何像前台服务一样启动 socket.io 以保持连接并监听消息的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Android O 中保持服务在后台运行?

socket.io 是如何工作的

以打盹模式启动前台服务

如何使用 socket.io 断开所有套接字服务端?

iOS:让应用程序像服务一样运行

nginx、node.js 和 socket.io - 有工作婚姻吗?