开场白:squareup大法好啊
9月2号更新:
接收到message,如果用户在其他界面,我们可以使用 Notification给它更好的提醒。
NotifacationManagerUtils:
主要作用:
1.控制声音,控制震动
2.使用Rxjava2 控制短时间大量发送消息优化。
3.同一个人进入消息详情设置setChatUid,就可以屏蔽不在继续弹该ChatUid的用户消息。
8月22号更新:
起因:类似于假链接的意思(服务器认为你断开,客户端实际链接上。其他这里面有很多因素,具体需要自己去排除)
解决办法:
客户端与服务端指定一套规则:模拟WebSocket ping pong 动作,客户端 x分钟 通过WebSocket给服务器发送一次ping,服务器回复pong。如果x秒后接收到,就说明链接上了。如果x秒后没有接收到,并且自己认为自己是链接状态 那就断开重新链接。
——————————————————————————–分割线——————————————————-
简述:关于一些推送和IM 功能,可能大家都采用的是第三方(环信,融云 极光等)
但是我们由于这一块的业务目前还是特别大,就自己搭建了聊天和推送系统。
代码中如果有不足的地方,可以相互讨论一下,提供一个我在实际项目中,所遇到困难,解决的思路。
利与弊:
利:
弊:
WebSocket设计思路:
WebSocket 使用方法:
ChatController.getInstance().startConnection(mcontext);
可以查看下面具体实现。
WebSocket 源码(持续优化)
/**
* 作者:taolipeng
* 邮箱:[email protected]
*
*/
public class ChatController {
public static final int connecttimeout = 1 * 1000;
private Context mContext;
public ChatController() {
initConnection();
}
private void initConnection() {
Log.d("WebSocket", "初始化");
ResetRetry();
setConnected(false);
initServerUri();
WebSocketInit();
}
private void initServerUri() {
serverUri = "ws:ip:port" // 如: ws:192.168.0.1:80
}
private void closeWebScoket() {
if (mWebSocket == null) return;
mWebSocket.close(1000, null);
}
/**
* 重试机制
* 2*n n = 1—5
*
* 服务器异常情况下,链接超过五次。就不链接了
*/
private int RetryCount = 1;
private int RetrySecondTime = 0; // 秒
private boolean ErrorConnection = false;
private void retryConnection() {
RetrySecondTime = RetryCount * 2;
RetryCount++;
LogUtil.d("retryConnection", "retryConnection RetryCount =" + RetryCount);
if (RetryCount == 5) {
// WebSocket 在下一次进入 或者 成功链接
ErrorConnection = true;
return;
}
try {
RxTimerUtils.cancel();
RxTimerUtils.timer(RetrySecondTime, new RxTimerUtils.IRxNext() {
@Override
public void doNext(long number) {
}
@Override
public void complete() {
// 重新链接
BindBackstageWebSocketRule();
}
});
} catch (Exception e) {
}
}
// 重置重试机制
private void ResetRetry() {
RetryCount = 1;
RetrySecondTime = 0;
ErrorConnection = false;
}
private void WebSocketReceive(String message) {
LogUtil.e(debug, TAG, "获取到服务器信息---scoket【" + message + "】");
setConnected(true);
// 界面刷新传递。
if("pong".equals(message)){
//收到特定消息
ConnectionRealAlive = true;
}else if("letter".equals(message)){
// Notification 提醒
NotifacationManagerUtils.getIstance().ChatnotifyToBrand(mContext, type, msgItemsEntity)
//接收到信息,处理逻辑。
}
}
@NonNull
public static byte[] lock = new byte[0];
private static ChatController mWbController;
WebSocketListener socketListener;
boolean connected = false;
public boolean isConnected() {
return connected;
}
public void setConnected(boolean connected) {
this.connected = connected;
}
// 实际根据业务来
protected static String getToken() {
return "服务设置Token";
}
public static ChatController getInstance() {
synchronized (lock) {
if (mWbController == null) {//单例模式
mWbController = new ChatController();
}
return mWbController;
}
}
public void startConnection(Context context) {
if (ErrorConnection) {
LogUtil.i(debug, TAG, "【ChatController.startConnection(重试次数达到上限)】");
return;
}
mContext = context;
if (!checkParams()) {
LogUtil.i(debug, TAG, "【ChatController.receivedLoginBeat(有网络,用户未登录,无需服务器连接)】");
return;
}
if (isConnected()) {
LogUtil.i(debug, TAG, "【ChatController.receivedLoginBeat(有网络,服务器已经连接无需再次连接)】");
} else {
LogUtil.i(debug, TAG, "【ChatController.receivedLoginBeat(有网络,开始服务器连接...)】");
WebSocketInit();
}
}
public String serverUri;
public String header;
ConnectionSpec spec;
WebSocket mWebSocket;
OkHttpClient client;
WebSocketListener webSocketListener;
public String getServerUri() {
return serverUri;
}
public void setServerUri(String serverUri) {
this.serverUri = serverUri;
}
public String getHeader() {
return header;
}
public void setHeader(String header) {
this.header = header;
}
// wss 方式
private void WebSocketInit() {
// 登录情况下,开始链接
if (!initWebSocketListener()) {
return;
}
boolean isWss = checkServerUri(serverUri);
spec = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
.tlsVersions(TlsVersion.TLS_1_2)
.cipherSuites(CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA)
.build();
//创建WebSocket链接
OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder()
.retryOnConnectionFailure(true)//允许失败重试
.connectTimeout(2, TimeUnit.SECONDS)
.readTimeout(5, TimeUnit.SECONDS)
.writeTimeout(5, TimeUnit.SECONDS);
initHeader();
if (isWss) {
client = clientBuilder.connectionSpecs(Collections.singletonList(spec)).build();
} else {
client = clientBuilder.build();
}
Request request = new Request.Builder().addHeader("token", header).url(serverUri).build();
mWebSocket = client.newWebSocket(request, webSocketListener);
if (mWebSocket==null) return;
client.dispatcher().executorService().shutdown();
}
/**
* @return true wss false ws
*/
private boolean checkServerUri(String url) {
if (url.regionMatches(true, 0, "ws:", 0, 3)) {
return false;
} else if (url.regionMatches(true, 0, "wss:", 0, 4)) {
return true;
}
return false;
}
private boolean initWebSocketListener() {
if (!checkParams()) {
return false;
}
webSocketListener = new WebSocketListener() {
@Override
public void onOpen(WebSocket webSocket, Response response) {
super.onOpen(webSocket, response);
mWebSocket = webSocket;
setConnected(true);
ResetRetry();
BindBackstageWebSocketRule();
IsWebSocketAliveController(); // 防止出现假链接状态
LogUtil.i(TAG, "已经成功连接到服务器【" + webSocket.request().url() + "】");
}
@Override
public void onMessage(WebSocket webSocket, String text) {
super.onMessage(webSocket, text);
WebSocketReceive(text);
}
@Override
public void onMessage(WebSocket webSocket, ByteString bytes) {
super.onMessage(webSocket, bytes);
}
@Override
public void onClosing(WebSocket webSocket, int code, String reason) {
super.onClosing(webSocket, code, reason);
LogUtil.e(TAG, "断开服务器连接【" + ",状态码: " + code + ",断开原因:" + reason + "】");
disconnected();
}
@Override
public void onClosed(WebSocket webSocket, int code, String reason) {
super.onClosed(webSocket, code, reason);
LogUtil.e(TAG, "断开服务器连接【" + ",状态码: " + code + ",断开原因:" + reason + "】");
disconnected();
}
@Override
public void onFailure(WebSocket webSocket, Throwable t, Response response) {
super.onFailure(webSocket, t, response);
LogUtil.e(TAG, "连接发生了异常【异常原因:" + t + "】 getCause =" + t.getCause() + " " + t.getMessage());
retryConnection();
disconnected();
}
};
return true;
}
private void IsWebSocketAliveController() {
SocketRealAlivetimer();
}
/**
* 替代ping pong
* 当open 的时候 发送tag
* 7秒的时候判断是否有收到回应,如果没有收到重新链接
* 如果有收到 ConnectionRealAlive 重置,重新定时.
*
*/
private void SocketRealAlivetimer() {
if (IsError) return;
if (!isConnected()) return; // 链接上了,进行检查.
try {
sendCommentMsg();// 发送特定tag ----> ping (服务器规定传输结构体)
SevenSeconds(); // 接收 pong 定时器.
LogUtil.d(debug, TAG, "【ChatController.sendText()】{ 发送特定tag }");
//发送tag IsSend 是否还在执行中
if (RxTimerUtils.IsSend("WebSocketAlive")) return;
RxTimerUtils.timer(WebSocketRealAliveTime, new RxTimerUtils.IRxNext() {
@Override
public void doNext(long number) {
}
@Override
public void complete() {
LogUtil.d(debug, TAG, "【ChatController】{ SocketRealAlivetimer complete }");
// 启动定时
SocketRealAlivetimer();
}
}, "WebSocketAlive");
} catch (Exception e) {
IsError = true;
}
}
/// 7秒接收
private void SevenSeconds() {
try {
//发送tag
if (RxTimerUtils.IsSend("Fivetimer")) return;
RxTimerUtils.timer(7, new RxTimerUtils.IRxNext() {
@Override
public void doNext(long number) {
}
@Override
public void complete() {
LogUtil.d(debug, TAG, "【ChatController】{ SevenSeconds complete }");
LogUtil.d(debug, TAG, "【ChatController】{ 特定tag 7秒内有回复}");
if (ConnectionRealAlive == false) {
LogUtil.d(debug, TAG, "【ChatController】{ 特定tag 7秒内无回复,重新链接}");
BindBackstageWebSocketRule(); // 断开重连
}
ConnectionRealAlive = false;
}
}, "Fivetimer");
} catch (Exception e) {
IsError = true;
}
}
//正常情况下断开通知后台接触绑定 // 如果业务不需要,可以remove.
public void BindBackstageWebSocketRule() {
if (ErrorConnection) {
LogUtil.i(debug, TAG, "【ChatController.startConnection(重试次数达到上限)】");
return;
}
try {
if (!isConnected()) {
startConnection(mContext);
} else {
if (mWebSocket != null)
mWebSocket.send(content);
}
} catch (Exception e) {
e.printStackTrace();
LogUtil.e(debug, TAG, "【ChatController.sendText()】【e=" + e + "】");
} catch (java.lang.AssertionError e) { //防止SocketTimeoutException
e.printStackTrace();
}
}
/**
* 通知后台解绑webSocket // 如果业务不需要,可以remove.
*/
public void UBindBackstageWebSocketRule() {
try {
if (!isConnected()) {
//连接断开
return;
}
HashMap hashMap = new HashMap<>();
if (mWebSocket == null) return;
mWebSocket.send(content);
closeWebScoket();
} catch (Exception e) {
e.printStackTrace();
LogUtil.e(debug, TAG, "【ChatController.unBind()】【e=" + e + "】");
} catch (java.lang.AssertionError e) { //防止SocketTimeoutException
e.printStackTrace();
}
}
/**
* 检查参数
* 是否登录
*/
private boolean checkParams() {
boolean isOk = isLogin() //设置参数
if (userInfo == null) {
return false;
} else {
return true;
}
}
private void initHeader() {
header = getToken();
}
public void disconnected() {
setConnected(false);
mWebSocket = null;
}
// 发送信息
public void sendMsg(String content) {
if (!isConnected()) return;
// 判断是否链接----
if (mWebSocket != null) {
try {
// 发送信息
mWebSocket.send(content);
} catch (Exception e) {
e.printStackTrace();
LogUtil.e(debug, TAG, "【ChatController.sendText()】【e=" + e + "】");
} catch (java.lang.AssertionError e) { //防止SocketTimeoutException
e.printStackTrace();
}
}
}
private static final String TAG = LogUtil.DEGUG_MODE ? "ChatController" : ChatController.class.getSimpleName();
private static final boolean debug = true;
@NonNull
Handler mHander = new Handler();
@Nullable
Runnable mRunnable = null;
public void post(int what, Object object) {
// 发送信息
}
}
NotifacationManagerUtils.class
/**
* Created by zuber on 2017/2/4.
* 维护 notifycation 的id 唯一性
*/
public class NotifacationManagerUtils {
private static final String TAG = "NotifacationManagerUtils";
private int Number = 0;
private String chatUid = "";
// 定义一个私有构造方法
private NotifacationManagerUtils() {
}
//定义一个静态私有变量(不初始化,不使用final关键字,使用volatile保证了多线程访问时instance变量的可见性,避免了instance初始化时其他变量属性还没赋值完时,被另外线程调用)
private static volatile NotifacationManagerUtils instance;
//定义一个共有的静态方法,返回该类型实例
public static NotifacationManagerUtils getIstance() {
// 对象实例化时与否判断(不使用同步代码块,instance不等于null时,直接返回对象,提高运行效率)
if (instance == null) {
//同步代码块(对象未初始化时,使用同步代码块,保证多线程访问时对象在第一次创建后,不再重复被创建)
synchronized (NotifacationManagerUtils.class) {
//未初始化,则初始instance变量
if (instance == null) {
instance = new NotifacationManagerUtils();
}
}
}
return instance;
}
public int getNumber() {
return Number++ % 120;
}
public void setNumber(int number) {
Number = number;
}
//统一化 Notification
//http://blog.csdn.net/u012124438/article/details/53574649
private static LargeDataObservable largeDataObservable;
//使用Rxjava重构,避免用户尽可能受到打扰。
private void RxMessageInterval(String type) {
notifytype=type;
if (largeDataObservable != null) {
largeDataObservable.setMessage("empty");
return;
}
largeDataObservable = new LargeDataObservable();
largeDataObservable
.debounce(800, TimeUnit.MILLISECONDS)
.map(new Function() {
@Override
public String apply(CharSequence charSequence) throws Exception {
return charSequence.toString();
}
}).observeOn(AndroidSchedulers.mainThread())
.subscribe(VoiceAndShockController());
}
// all 所有 type voice 声音 Shock 震动 BreathingLamp 呼吸灯
@NonNull
private DefaultObserver VoiceAndShockController() {
return new DefaultObserver() {
@Override
public void onNext(@NonNull String recipeWrapper) {
LogUtil.d(TAG, "VoiceAndShockController--- 进来了");
switch (notifytype) {
case "all":
ring();
vibrator();
break;
case "voice":
ring();
break;
case "Shock":
vibrator();
break;
case "BreathingLamp":
break;
}
}
@Override
public void onError(@NonNull Throwable e) {
}
@Override
public void onComplete() {
}
};
}
public boolean ChatnotifyToBrand(@NonNull Context mContext, @NonNull String type, MsgItemsEntity chatBean) {
String chatUid = chatBean.getChat_uid();
if (filterChatMessage(type, chatUid)) return false;
String tickerText = chatBean.getAuthor() + "发来一条私信";
// 建立一个通知实例,第一个参数是图片,第二个标题栏上显示的文字,第三个是时间
Notification.Builder builder = new Notification.Builder(mContext);
builder.setContentTitle(tickerText);//设置下拉列表里的标题
builder.setContentText(chatBean.getContent());//设置上下文内容
builder.setSmallIcon(R.drawable.ic_launcher48);
builder.setTicker(tickerText);
builder.setOngoing(false);
builder.setWhen(System.currentTimeMillis());
builder.setAutoCancel(true);
if (type.equalsIgnoreCase("letter")) {
Intent intent = new Intent(mContext, 聊天详情.class);
//chatBean.setUnread_count(0);
intent.putExtra("item", chatBean);
builder.setContentIntent(PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT));
}
Notification notification = builder.getNotification();//获取一个Notification
NotificationManager mNotificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); // 初始化管理器
boolean voice = NewMessageSettingController.getVoice(mContext); // 声音
boolean Shock = NewMessageSettingController.getShock(mContext); // 震动
if (voice == true && Shock == true) {
RxMessageInterval("all");
LogUtil.d(TAG, "震动 声音");
} else if (voice == false && Shock == false) {
// LogUtil.d(TAG, "呼吸灯");
} else if (voice == true && Shock == false) {
RxMessageInterval("voice");
LogUtil.d(TAG, "声音");
} else {
RxMessageInterval("Shock");
LogUtil.d(TAG, "震动");
}
mNotificationManager.notify(NotifacationManagerUtils.getIstance().getNumber(), notification);
return true;
}
private String notifytype="all";
//第0个表示等待时长,第1个表示震动时长,第2个等待时长,第3个震动时长....依次循环。
private void vibrator() {
LogUtil.i(debug, TAG, "【ChatController.vibrator】");
Vibrator vibrator = (Vibrator) ZuberApplication.getInstance().getSystemService(Service.VIBRATOR_SERVICE);
vibrator.vibrate(new long[]{200, 10, 100,200}, -1);
}
private void ring() { //
// 声音
LogUtil.i(debug, TAG, "【ChatController.ring】");
Uri notification = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
Ringtone r = RingtoneManager.getRingtone(ZuberApplication.getInstance(),
notification);
r.play();
}
//清除所有通知栏
public void clearAllNotification(Context mContext) {
NotificationManager mNotificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); // 初始化管理器
mNotificationManager.cancelAll();
}
//过滤 在同一个聊天界面 不断提示 notification 问题
public boolean filterChatMessage(@NonNull String type, @NonNull String chatUid) {
if (type.equalsIgnoreCase("letter")) {
if (chatUid.equalsIgnoreCase(getChatUid())) {
return true;
} else {
return false;
}
}
return false;
}
public String getChatUid() {
return chatUid;
}
public void setChatUid(String chatUid) {
this.chatUid = chatUid;
}
}
需要依赖的库
compile 'com.squareup.okhttp3:okhttp:3.8.0'
compile 'com.squareup.retrofit2:retrofit:2.2.0'
compile 'com.squareup.retrofit2:adapter-rxjava2:2.2.0'
//RxAndroid
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
compile 'io.reactivex.rxjava2:rxjava:2.0.1'
compile 'com.jakewharton.rxbinding2:rxbinding:2.0.0'
//Rxjavalife
compile 'com.trello.rxlifecycle2:rxlifecycle:2.0.1'
compile 'com.trello.rxlifecycle2:rxlifecycle-components:2.0.1'
compile 'com.trello.rxlifecycle2:rxlifecycle-android:2.0.1'