在上一篇文章中,讲解了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四大组件之一,其它三大组件分别是:Activity
、ContentProvider
、Broadcast Receiver
。Service
是一个应用程序组件,表示应用程序希望在不与用户交互的情况下执行更长时间运行的操作,或者提供供其他应用程序使用的功能。
Service
启动的方式不同,会执行不同的生命周期,实现的效果各有差别,使用startService()
启动Service
,当这个方法执行时,服务就会启动并且可以无限期地在后台运行。而使用bindService()
启动的Service
,仅在组件绑定到它时运行,服务与其所有客户端解除绑定后,系统会将其销毁。
Service生命周期如下,具体详情见:Service
在后台接收消息,就要求Service
在销毁App后还在继续执行,用startService()
去启动Service
才能实现我们的需求。
Notification
Service
继承了Context
类,使得我们可以直接调用getSystemService(NOTIFICATION_SERVICE)
方法得到一个NotificationManager
,通过在Service
的onStartCommand(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 将服务置于前台状态才不会被系统随意杀死(有一种保活方式是在Service
的onDestroy()
方法中启动Service
,即Serivce
销毁自启),这不就类似各大音乐平台app进入后台运行的处理方式嘛?
后面,我也的确加上了startForeground(int, Notification)
API的调用,但效果已与使用NotificationManager.notify(int id, Notification notification)
有所差别。
小结
- 运行程序在手机,IP地址输入无误,连接Socket超时也许是电脑防火墙未关闭,导致手机无法访问电脑IP地址。
- 点击下载源码