Socket 多人聊天室的实现(App后台接收消息的处理)(二)

在上一篇文章中,讲解了Socket连接过程并实现连接,匿名用户发消息互怼,本篇文章将实现App后台运行程序接收消息,在状态栏显示消息的效果。

Socket 多人聊天室的实现系列文章:

封装Socket

开始的第一步,要把Socket封装起来,本篇文章的Socket代码不再是Activity类用得到,Service同样需要使用。

public class SocketUtil {
    private static String TAG = "SocketUtil";
    private static Socket socket = null;
    private static JSONObject jsonObject;
    private static String name;
    private static ExecutorService mExecutorService = null;
    private static PrintWriter printWriter;
    private static BufferedReader in;

    /**
     * 初始化用户名,随机生成一个用户名称
     */  
     public static void initUser() {
        try {
            jsonObject = new JSONObject();
            name = RandomGenerateName.randomName(true, 3);
            jsonObject.put("name", name);
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

    /**
     * 发起连接
     */
    public static void connectSocket(MessageNotiface notiface) {
        mExecutorService = Executors.newCachedThreadPool();
        mExecutorService.execute(new Runnable() {
            @Override
            public void run() {
                //可以考虑在此处添加一个while循环,结合下面的catch语句,实现Socket对象获取失败后的超时重连,直到成功建立Socket连接
                try {
                    // 初始化socket地址、端口号
                    socket = new Socket(Config.SOCKET_HOSE, Config.SOCKET_PORT);
                    printWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter(   //步骤二
                            socket.getOutputStream(), "UTF-8")), true);
                    in = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
                    // 发送消息到后台
                    printWriter.println(jsonObject);

                    try {
                        String receiveMsg = "";
                        while (true) {
	                        // 读取后台Socket发送过来的消息,此处会阻塞,获取不到消息会停止往下执行
                            if ((receiveMsg = in.readLine()) != null) {
                                try {
                                    JSONObject jsonObject = new JSONObject(receiveMsg);
                                    // 将消息传送到实现接口的地方
                                    notiface.onMessage(jsonObject);
                                } catch (JSONException e) {
                                    e.printStackTrace();
                                }
                            }
                        }
                    } catch (IOException e) {
                        Log.e("TAG", e.getMessage());
                    }
                } catch (Exception e) {
                    try {
                        //如果Socket对象获取失败,即连接建立失败,会走到这段逻辑
                        JSONObject json = new JSONObject();
                        json.put("name", Config.SUPER_ADMINISTRATOR);
                        json.put("msg", "连接聊天室失败,请检查聊天室IP、端口是否正确,并确认服务端是否已开启");
                        notiface.onMessage(json);
                    } catch (JSONException jsonException) {
                        Log.e(TAG, jsonException.getMessage());
                    }
                }
            }
        });
    }

    /**
     * 发送消息
     */
    public static void sendMessage(View view, String message) {
        if (!message.equals("")) {
            try {
                jsonObject.put("msg", message);
                mExecutorService.execute(new sendService(jsonObject.toString()));
            } catch (JSONException e) {
                e.printStackTrace();
            }
        } else {
            Snackbar.make(view, "发送内容不能为空,请重新输入", Snackbar.LENGTH_LONG).show();
        }
    }

    /**
     * 断开连接(通过向服务器发送消息让服务器管理对应的socket连接)
     */
    public static void disconnectSocket() {
        try {
            jsonObject.put("msg", Config.SOCKET_DISCONNECT_MESSAGE);
            mExecutorService.execute(new sendService(jsonObject.toString()));
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

	/**
     * 发送消息的线程
     */
    public static class sendService implements Runnable {
        private String msg;

        sendService(String msg) {
            this.msg = msg;
        }

        @Override
        public void run() {
            if (printWriter != null) {
                printWriter.println(msg);
            }
        }
    }

    public interface MessageNotiface {
        void onMessage(JSONObject json);
    }
}

使用需要先初始化,如下:

// 连接Sokcer
SocketUtil.initUser();
SocketUtil.connectSocket(new SocketUtil.MessageNotiface() {
    @Override
    public void onMessage(JSONObject json) {
        // socket响应的消息
    }
});

Service

App进入后台后的各种操作,离不开Android四大组件的Service,先来简单了解一下Service

  • 简介
    Service是Android四大组件之一,其它三大组件分别是:ActivityContentProviderBroadcast ReceiverService是一个应用程序组件,表示应用程序希望在不与用户交互的情况下执行更长时间运行的操作,或者提供供其他应用程序使用的功能。
    Service启动的方式不同,会执行不同的生命周期,实现的效果各有差别,使用startService()启动Service,当这个方法执行时,服务就会启动并且可以无限期地在后台运行。而使用bindService()启动的Service,仅在组件绑定到它时运行,服务与其所有客户端解除绑定后,系统会将其销毁。
    Service生命周期如下,具体详情见:Service

在这里插入图片描述
在后台接收消息,就要求Service在销毁App后还在继续执行,用startService()去启动Service才能实现我们的需求。

Notification

Service继承了Context类,使得我们可以直接调用getSystemService(NOTIFICATION_SERVICE)方法得到一个NotificationManager,通过在ServiceonStartCommand(Intent intent, int flags, int startId)生命周期配置上一系列参数,当App运行在后台时,通知栏就可以展示最新接收到的消息。

public class SockerService extends Service {
    /**
     * 通知的标识符,唯一
     */
    private final int NotifyId = 0;

    public SockerService() {
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d("SockerService", "Service创建成功");
    }

    /**
     * startService启动Service触发的生命周期
     *
     * @param intent
     * @param flags
     * @param startId
     * @return
     */
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        super.onStartCommand(intent, flags, startId);
        Log.d("SockerService", "Service启动成功");
        SocketUtil.initUser();
        SocketUtil.connectSocket(new SocketUtil.MessageNotiface() {
            @Override
            public void onMessage(JSONObject json) {
                // socket接收到的消息
                NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
                String notificationId = UUID.randomUUID().toString();
                String notificationName = UUID.randomUUID().toString();
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                    // 高版本的模拟器或手机还需要开启渠道才能显示通知
                    NotificationChannel notificationChannel = new NotificationChannel(notificationId, notificationName, NotificationManager.IMPORTANCE_HIGH);
                    notificationManager.createNotificationChannel(notificationChannel);
                }
                NotificationCompat.Builder builder = new NotificationCompat.Builder(SockerService.this, notificationId);
                // 实例化一个意图,当点击通知时会跳转执行这个意图
                Intent intent1 = new Intent(SockerService.this, MainActivity.class);
                // 将意图进行封装
                PendingIntent pendingIntent = PendingIntent.getActivity(SockerService.this, 0, intent1, PendingIntent.FLAG_CANCEL_CURRENT);
                //设置Notification的点击之后执行的意图
                builder.setContentIntent(pendingIntent);
                builder.setSmallIcon(R.mipmap.ic_launcher);
                try {
                    String name = json.getString("name");
                    String msg = json.getString("msg");
                    builder.setContentTitle(name + "发来了一条消息");
                    builder.setContentText(msg);
                    notificationManager.notify(NotifyId, builder.build());
//                    startForeground(28, builder.build());
                } catch (JSONException e) {
                    e.printStackTrace();
                }
            }
        });
        // 返回值为如果服务被异常杀掉,系统就会重启该服务,并传入Intent的原值
        return START_STICKY_COMPATIBILITY;
    }

    /**
     * bindService启动Service触发的生命周期
     *
     * @param intent
     * @return
     */
    @Override
    public IBinder onBind(Intent intent) {
        // MyIBinder myIBinder = new MyIBinder();
        return null;
    }

    /**
     * bindService取消绑定Service触发的生命周期
     *
     * @param conn
     */
    @Override
    public void unbindService(ServiceConnection conn) {
        super.unbindService(conn);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d("SockerService", "Service断开连接并尝试重新创建");
        Intent intent = new Intent(this, SockerService.class);
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
            startForegroundService(intent);
        } else {
            startService(intent);
        }
        // Service销毁时断开连接
        SocketUtil.disconnectSocket();
    }
}

在这里插入图片描述
but,事情到此尚未结束。博主在测试过程中发现,有几次App进入后台一段时间后,就再也接手不到新消息了,后台打印一下日志,发现Service未经过博主大大的允许,竟然断开了连接!!!
在这里插入图片描述
在这里插入图片描述
想不明白问题所在,干脆翻一下开发文档,别说,真让我找着了。
在这里插入图片描述
为了多出点内存让系统运行更加的流畅,系统会在手机内存不足,杀死部分服务。也因此,使用Service在后台运行,最好使用startForeground(int, Notification)API 将服务置于前台状态才不会被系统随意杀死(有一种保活方式是在ServiceonDestroy()方法中启动Service,即Serivce销毁自启),这不就类似各大音乐平台app进入后台运行的处理方式嘛?

后面,我也的确加上了startForeground(int, Notification)API的调用,但效果已与使用NotificationManager.notify(int id, Notification notification)有所差别。

小结

  • 运行程序在手机,IP地址输入无误,连接Socket超时也许是电脑防火墙未关闭,导致手机无法访问电脑IP地址。
  • 点击下载源码

参考文档:
关于Android Service真正的完全详解,你需要知道的一切

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
C语言和Socket可以实现多人聊天室。 在C语言中,可以使用Socket编程来实现网络通信功能。Socket是一种网络通信的API,可以通过创建套接字(socket)来进行数据传输和通信。 实现多人聊天室的主要步骤如下: 1. 创建服务器端程序:首先,需要创建一个服务器端程序,用于接收处理客户端的连接请求。服务器端程序可以创建一个套接字,并进行绑定、监听等操作,用于等待客户端的连接请求。 2. 创建客户端程序:然后,需要创建多个客户端程序,用于连接服务器并进行聊天。客户端程序可以通过创建套接字,并与服务器端建立连接,然后进行聊天和数据交换。 3. 客户端与服务器端的通信:一旦客户端和服务器端建立了连接,它们就可以通过套接字进行数据的传输和通信。客户端可以向服务器发送消息,服务器可以接收处理这些消息,然后将消息转发给其他客户端。 4. 多人聊天实现:为了实现多人聊天功能,服务器端需要维护一个客户端列表,用于存储连接到服务器的所有客户端。当某个客户端发送消息时,服务器可以将消息发送给所有其他客户端,从而实现多人聊天的效果。客户端也可以接收其他客户端发送的消息,并进行展示。 总结来说,使用C语言和Socket编程可以实现多人聊天室。服务器端负责接收处理连接请求,并转发消息给其他客户端;客户端负责连接服务器,并发送、接收和展示消息。这样就可以实现多个用户之间的实时聊天。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

宾有为

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值