前记
目前实现推送的方式是很多的,可以用一些大公司推出的sdk,如:友盟,个推,小米。。。,而实现推送功能也有许多成熟的框架可供使用:像基于socket的mina,socket.io,基于websocket的androidAsyn,autobahn,java-websocket等,我下面要说的是java-websocket简单的封装实现推送和单线登录功能
步骤:
- java-websocket导包可以在gitHub上找到,这里不做介绍;
- 建立一个单例化的Link类,这里就不用介绍了。
- 建立一个websocket任务类来实现Runnable,在newClient中回调其中的四个方法,在每个方法中写相应的逻辑
- 封装启用socket方法和关闭socket方法,其中启用socket方法就是如果没有该任务类就new 该任务类,关闭该任务类就是直接调用websocket的close方法
- 添加发送方法 ,通过调用websocket的send方法,一定要注意该方法一定要用try...catche来捕获,不然会报各种错误
- 为了让后台知道我们是否还连接着我们必须要间隔一段时间来发送心跳,从而进行相应的保活
- 由于不可预知的原因会使连接断开,必须规定的时间内进行相应相应的重连
- 建立一个服务类,用于启用该link类,这样就可以在该link类中启用socket任务
- 通过接口回调的方式将client中回调方法中onMessage中的数据回调给相应的服务,通过该服务来处理接收到的数据,通过广播的形式将相应的数据返回给程序中。
参考
- android中websocket应用
- android中脱离WebView使用Websocket实现群聊和推送功能
- websocket与socket.io介绍
- java-websocket所在的传送门
下面是我写的程序
public class JavaSocketLink {
public static final String TAG = JavaSocketLink.class.getSimpleName();
private Context context;
private WebSocketClient mClient;
private String mUrl = null;
private WebSocketTask socketTask;
private SendMessageListener listener;
private boolean isConnect = false; // 判断是否连接
// private boolean isClose = false; // 判断是否关闭
private boolean isLoging = false; // 判断是否登录
private boolean isError = false ; // 判断是否调用了error
private String uid = null; // 当发送消息需要携带的uid,用于检查用户是否真实
private static JavaSocketLink instance = null; //用于接收对象
private JavaSocketLink() { //私有化构造器 防止被调用
}
public synchronized static JavaSocketLink newInstance() { //单例化方法
if (instance == null) {
synchronized (JavaSocketLink.class) {
if (instance == null) {
instance = new JavaSocketLink();
}
}
}
return instance;
}
/**
* 开启socket任务
*
* @param context
* @param mUrl
*/
public void startWebSocketTask(Context context, String mUrl) {
this.context = context;
this.mUrl = mUrl;
if (instance != null) {
if (socketTask == null) { //如果socketTask没有被创建,就重新创建
socketTask = new WebSocketTask();
}
if (!Tools.isNetworkConnected(context)) {
ToastUtil.show(context, "请检查网络连接!");
return;
}
//每次启动任务的时候都将已经创建的任务定时器取消掉
stopKeepTimer(); // 关闭心跳计时器
stopReconnectTimer(); // 关闭相应的重连定时器
handleThread(); //单线程处理socket任务
}
}
/**
* 利用thread来处理socketTask任务
*/
private void handleThread() {
ExecutorService executor = Executors.newSingleThreadExecutor();
if(socketTask != null){
executor.execute(socketTask);
}
}
/**
* 关闭websocket服务
*/
public void shutDownSocketTask() {
if (socketTask != null && mClient != null) {
mClient.close(); //关闭相应的java-websocket
isConnect = false;
stopKeepTimer(); //关闭已有的保活timer
stopReconnectTimer(); // 关闭已有的重连服务
mClient = null;
}
}
/**
* 创建websocket任务
*/
private class WebSocketTask implements Runnable {
@Override
public void run() {
if (TextUtils.isEmpty(mUrl)) { //判断mUrl是否为空
Toast.makeText(context, "没有获取到连接地址", Toast.LENGTH_SHORT).show();
return;
}
try {
mClient = new WebSocketClient(new URI(mUrl), new Draft_17()) {
@Override
public void onOpen(ServerHandshake handshakedata) {
LogUtils.d(TAG, "---->>onOpen");
if (mClient != null && mClient.isOpen()) {
isConnect = true; //判断已经连接
}
}
@Override
public void onMessage(String message) {
LogUtils.d(TAG, "---->>onMessage"); //此时说明已经连接,并且已经有数据过来
if (!TextUtils.isEmpty(message)) {
isConnect = true; //判断已经连接上了
isError = false;
listener.onSendMessageListener(message); // 将获取到的message回调给相应服务
}
}
@Override
public void onClose(int code, String reason, boolean remote) {
LogUtils.d(TAG, "---->>onClose");
//关闭应该调用三次重连服务
if (null != mClient) {
LogUtils.d(TAG, "---->>onClose1");
isConnect = false;
if(!isError){
LogUtils.d(TAG, "---->>onClose2");
reconnect();
}
}
}
@Override
public void onError(Exception ex) {
LogUtils.d(TAG, "--->>onError");
//当出现错误时候,应该去关闭相应socket连接
// shutDownSocketTask();
isError = true;
// shutDownSocketTask();
// stopReconnectTimer(); // 如果调用的onerror方法,
}
};
connectSocket(); //进行相应的连接
} catch (URISyntaxException e) {
e.printStackTrace();
LogUtils.d(TAG, "socket is onError :" + e.getMessage());
//当解析错误的时,关闭相应的socket连接
shutDownSocketTask();
}
}
}
public interface SendMessageListener {
void onSendMessageListener(String message);
}
public void setOnMessageListener(SendMessageListener listener) {
this.listener = listener;
}
public void connectSocket() {
if (mClient != null && !mClient.isOpen()) {
// mClient.connect();
mClient.connect();
}
}
/**
* 发送消息
*
* @param msg
*/
public void sendMessage(final String msg) {
if (mClient != null && socketTask != null && isConnect && !mClient.isClosed()) {
try {
mClient.send(msg);
} catch (Exception e) {
e.printStackTrace();
}
} else {
// Toast.makeText(context, "消息发送失败", Toast.LENGTH_SHORT).show();
}
}
private Timer keepTimer = null; //发送心跳包的定时器
// private long kPeriod = 3 * 1000; // 设置心跳保活时间
private long kPeriod = 3 * 10000; // 设置心跳保活时间
/**
* 保活任务类
*/
TimerTask keepTask = new TimerTask() {
@Override
public void run() {
if (!TextUtils.isEmpty(uid) && null != instance && isConnect) {
instance.sendMessage(SocketAction.buildKeepAliveJsonString(uid));
}
}
};
// /**
// * 保活任务类
// */
// private class KeepTask extends TimerTask {
// @Override
// public void run() {
// if (!TextUtils.isEmpty(uid) && null != instance && isConnect) {
// instance.sendMessage(SocketAction.buildKeepAliveJsonString(uid));
// }
// }
// }
/**
* 启动保活定时器
*/
public void startKeepTimer() {
if (keepTimer == null) {
keepTimer = new Timer(); // 创建保活定时器对象
}
if (keepTimer != null) {
if(keepTask != null){
keepTimer.schedule(keepTask, 1000, kPeriod);
}
}
}
/**
* 关闭保活定时器
*/
public void stopKeepTimer() {
if (keepTimer != null) {
keepTimer.cancel();
keepTimer = null;
}
}
private Timer reconnectTimer = null; //重连定时器
private int count = 0; // 用于计数,判断到一定count的情况下(这里规定进行三次的重连,每次间隔时间为5s,10s,15s)停止重连服务
private long period = 0;
/**
* 重连任务
*/
TimerTask reconnectTask = new TimerTask() {
@Override
public void run() {
count++;
if (!isConnect)
reconnectSocket();
}
};
/**
* 重新进行socket连接
*/
private void reconnectSocket() {
// new Thread(socketTask).start();
handleThread();
}
/**
*进行重连
*/
private void reconnect() {
if (count < 3) {
if (reconnectTimer == null) {
reconnectTimer = new Timer();
}
// period += 5 * 1000;
period = 5000;
reconnectTimer.schedule(reconnectTask, 100, period);
} else {
//如果三次都没重连上
count = 0;
shutDownSocketTask();
}
}
/**
* 关闭重连timer
*/
private void stopReconnectTimer() {
if (reconnectTimer != null) {
reconnectTimer.cancel();
reconnectTimer = null;
}
}
public String getmUrl() {
return mUrl;
}
public void setmUrl(String mUrl) {
this.mUrl = mUrl;
}
public boolean isConnect() {
return isConnect;
}
public void setConnect(boolean connect) {
isConnect = connect;
}
public boolean isLoging() {
return isLoging;
}
public void setLoging(boolean loging) {
isLoging = loging;
}
public String getUid() {
return uid;
}
public void setUid(String uid) {
this.uid = uid;
}
}
public class PushMessageService extends Service implements JavaSocketLink.SendMessageListener {
public static final String TAG = PushMessageService.class.getSimpleName();
private Context context;
// private SocketLink instance = null;
private JavaSocketLink instance = null;
private String mUrl = Constants.SOCKET_LONG_CONNECTION;
private String loginToken = null;
private String keepAliveUid = null;
private String sendLoginJson = null;
private boolean isLogin;
private UserNewManager userManager = null;
private String fd = null;
@Override
public void onCreate() {
super.onCreate();
context = this;
if (instance == null) {
// instance = SocketLink.newInstance();
instance = JavaSocketLink.newInstance();
}
userManager = MyApplication.getInstance().getUserManager();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (Tools.isNetworkConnected(context)) { //判断是否联网
if (null != intent) {
loginToken = intent.getStringExtra(SocketAction.LOGIN_TOKEN); //获取所在activity中的相关参数信息
keepAliveUid = intent.getStringExtra(SocketAction.KEEP_ALIVE_UID);
if (instance == null) {
// instance = SocketLink.newInstance(); //判断是否已经进行了单例模式
instance = JavaSocketLink.newInstance(); //判断是否已经进行了单例模式
}
instance.setOnMessageListener(this); //进行接收消息的接口回调
instance.setUid(keepAliveUid); //设置uid
// instance.cancelKeepAliveTimer(); //关闭相应的心跳定时器
// instance.cancelRecTimer(); //关闭重连定时器
if (!instance.isConnect()) { //如果没有连接就进行相应的连接
//判断是否已经连接
instance.startWebSocketTask(context, mUrl);
}
}
}
return START_STICKY;
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onDestroy() {
super.onDestroy();
// instance.setForceClose(true);
// instance.cancelKeepAliveTimer();
// instance.disconnect(); // 关闭
if (null != instance) {
instance.shutDownSocketTask();
}
}
@Override
public void onSendMessageListener(String message) {
LogUtils.d(TAG, "---->>onReceiveMessage: " + message);
try {
Gson gson = new Gson();
SocketModel socketModel = gson.fromJson(message, SocketModel.class);
if (socketModel.getCode() == 0) {
if (socketModel.getCmd().equals(SocketAction.OPEN)) {
//进行相应的登录,发送相关的登录信息
if (!TextUtils.isEmpty(loginToken)) {
sendLoginJson = SocketAction.buildLoginJsonString(loginToken);
// instance.send(sendLoginJson);
instance.sendMessage(sendLoginJson);
}
} else if ((socketModel.getCmd().equals(SocketAction.LOGIN)) && socketModel.getCode() == 0) {
if (null == socketModel.getData()) {
return;
}
SocketModel beans = gson.fromJson(message, new TypeToken>() {
}.getType());
if (beans != null && !TextUtils.isEmpty(beans.getData().getFd())) {
fd = beans.getData().getFd();
}
isLogin = true;
// instance.startKeepAliveTimer();
instance.startKeepTimer();
} else if (socketModel.getCmd().equals(SocketAction.NO_READ_MSG)) {
//这个已经登录,并且获取到了相应的未读数
isLogin = true;
Bundle bundle = new Bundle();
bundle.putString(SocketAction.SEND_NORMAL_MESSAGE, message);
sendBroadCast(bundle);
} else if (socketModel.getCmd().equals(SocketAction.CAT_PUSH_MSG) && isLogin) {
Bundle bundle = new Bundle();
bundle.putString(SocketAction.SEND_SCI_MESSAGE, message);
sendBroadCast(bundle);
} else if (socketModel.getCmd().equals(SocketAction.ORG_PUSH_MSG) && isLogin) {
Bundle bundle = new Bundle();
bundle.putString(SocketAction.SEND_ORG_MESSAGE, message);
sendBroadCast(bundle);
} else if (socketModel.getCmd().equals(SocketAction.LOGOUT) && isLogin) {
SocketModel beans = gson.fromJson(message, new TypeToken>() {
}.getType());
LogUtils.d(TAG, "---->>fd = " + fd + "::getFd" + beans.getData().getFd());
if (null != beans.getData() && !TextUtils.isEmpty(beans.getData().getFd())) {
if (fd.equals(beans.getData().getFd())) {
logout(socketModel);
}
}
}
} else if (socketModel.getCode() == 100100002 || socketModel.getCode() == 100100001) {
logout(socketModel);
} else {
Toast.makeText(context, TextUtils.isEmpty(socketModel.getMsg()) ? "socket连接失败" : socketModel.getMsg(), Toast.LENGTH_SHORT).show();
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 退出登录
*
* @param socketModel
*/
private void logout(SocketModel socketModel) {
// instance.closeWebSocketTask();
instance.shutDownSocketTask();
//解绑个推
logToken();
//删除数据库数据
DataSupport.deleteAll(UserDateSourceBean.class); //删除数据库中多余的数据
MyApplication.getInstance().setUser(null); //将用户数据清空
// ActivityUtils.removeAll(); //清除所有的activity
//关闭服务socket连接
Intent intent = new Intent(context, LoginActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(LoginActivity.LOGOUT_MSG, socketModel.getMsg());
intent.putExtra("isPush", true);
startActivity(intent); //跳转到相应的登录界面
}
/**
* 发送广播信息
*
* @param bundle
*/
public void sendBroadCast(Bundle bundle) {
Intent intent = new Intent();
intent.putExtras(bundle);
intent.setAction(SocketAction.SEND_BROADCAST_MESSAGE_ACTION);
sendBroadcast(intent);
}
/**
* 后台个推推
*/
void logToken() {
TreeMap map1 = new TreeMap<>();
String full2 = Constants.BASE_URL_NEW + Constants.LOGOUTGT;
String sn = Tools.getSignHttpSign(map1, Constants.LOGOUTGT);
RestClient.getInstance().logOut(Constants.NEW_URL, sn, map1).enqueue(new Callback() {
@Override
public void onResponse(Call call, Response response) {
if (response.isSuccessful()) {
if (response.body().getCode() == 0) {
SharedPreferencesUtil.setString(context, "token", "");
SharedPreferencesUtil.setString(context, "uid", "");
} else {
if (response.body().getCode() == 100000004) {
} else {
}
}
SharedPreferencesUtil.setString(context, "token", "");
SharedPreferencesUtil.setString(context, "uid", "");
} else {
if (response.body() != null) {
} else {
}
SharedPreferencesUtil.setString(context, "token", "");
SharedPreferencesUtil.setString(context, "uid", "");
}
}
@Override
public void onFailure(Call call, Throwable t) {
SharedPreferencesUtil.setString(context, "token", "");
SharedPreferencesUtil.setString(context, "uid", "");
}
});
}
}
public class SocketAction {
public static final String LOGIN_TOKEN = "loginToken"; //发送登录的token
public static final String KEEP_ALIVE_UID = ""; // 发送心跳时候需要发送的uid
public static final String SEND_LOGIN_MESSAGE = "sendLoginMessage"; //发送登录信息
public static final String RECEIVE_LOGIN_MESSAGE = "receiveLoginMessage"; // 接收登录信息
public static final String SEND_NORMAL_MESSAGE = "sendNormalMessage"; // 发送一般信息
public static final String RECEIVE_NORMAL_MESSAGE = "receiverNormalMessage"; // 接收一般信息
public static final String SEND_SCI_MESSAGE = "sendSciMessage"; //推送科猫小助手消息
public static final String SEND_ORG_MESSAGE = "sendOrgMessage"; //推送组织咨询消息
public static final String OPEN = "open"; //打开连接
public static final String LOGIN = "login"; //登录
public static final String NO_READ_MSG= "noreadNum"; //未读信息
public static final String CAT_PUSH_MSG= "catX_pushall"; //科猫圈推送信息
public static final String ORG_PUSH_MSG ="organizationX_pushall"; //组织推送信息
public static final String LOGOUT = "logout"; //退出登录
public static final String HEARTBEAT = "heartbeat";
// 广播action信息
public static final String SEND_BROADCAST_MESSAGE_ACTION = "com.scimall.broadcast.action";
/**
* 向服务器端发送登录信息
*/
public static String buildLoginJsonString(String token){
StringBuilder builder = new StringBuilder();
builder.append("{\"cmd\":\"login\",\"p\":{\"token\":\"");
builder.append(token);
builder.append("\"}}");
return builder.toString();
}
/**
* 向服务器端发送的心跳信息,这样可以让后台知道前端仍然活着,防止后台不知道前台是否活着
* @param uid
* @return
*/
public static String buildKeepAliveJsonString(String uid){
StringBuilder builder = new StringBuilder();
builder.append("{\"cmd\":\"heartbeat\",\"p\":{\"uid\":\"");
builder.append(uid);
builder.append("\"}}");
return builder.toString();
}
}