Okhttp WebSocket 优化总结
来源:互联网 发布:淘宝售后客服绩效考核 编辑:程序博客网 时间:2024/06/08 12:09
开场白:squareup大法好啊
9月2号更新:
接收到message,如果用户在其他界面,我们可以使用 Notification给它更好的提醒。
NotifacationManagerUtils:
主要作用:
1.控制声音,控制震动
2.使用Rxjava2 控制短时间大量发送消息优化。
3.同一个人进入消息详情设置setChatUid,就可以屏蔽不在继续弹该ChatUid的用户消息。
8月22号更新:
起因:类似于假链接的意思(服务器认为你断开,客户端实际链接上。其他这里面有很多因素,具体需要自己去排除)
解决办法:
客户端与服务端指定一套规则:模拟WebSocket ping pong 动作,客户端 x分钟 通过WebSocket给服务器发送一次ping,服务器回复pong。如果x秒后接收到,就说明链接上了。如果x秒后没有接收到,并且自己认为自己是链接状态 那就断开重新链接。
——————————————————————————–分割线——————————————————-
简述:关于一些推送和IM 功能,可能大家都采用的是第三方(环信,融云 极光等)
但是我们由于这一块的业务目前还是特别大,就自己搭建了聊天和推送系统。
代码中如果有不足的地方,可以相互讨论一下,提供一个我在实际项目中,所遇到困难,解决的思路。
利与弊:
利:
- 第三方 集成简单,方便使用,持续有团队优化。
- 自己搭建 扩展性高,数据 安全性比较高(提升到https)
弊:
- 第三方数据相比自己搭建安全性差一些,所有数据都经过第三方。
- 自己搭建开发周期时间长,而且所用到技术需要经过严格测试并且加入特殊异常处理机制, 扩展功能比较麻烦(因为支持功能设计前后端的设计)。
WebSocket设计思路:
- 参数初始化(断线重连次数,连接状态)
- 连接服务器设置。
- 连接WebSocket。
- 定时器查看连接状态 // 定时器实现可以搜索google
WebSocket 使用方法:
ChatController.getInstance().startConnection(mcontext);
可以查看下面具体实现。
WebSocket 源码(持续优化)
/** * 作者:taolipeng * 邮箱:15921216945@163.com * */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 * <p> * 服务器异常情况下,链接超过五次。就不链接了 */ 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<String, Object> 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<CharSequence, String>() { @Override public String apply(CharSequence charSequence) throws Exception { return charSequence.toString(); } }).observeOn(AndroidSchedulers.mainThread()) .subscribe(VoiceAndShockController()); } // all 所有 type voice 声音 Shock 震动 BreathingLamp 呼吸灯 @NonNull private DefaultObserver<String> VoiceAndShockController() { return new DefaultObserver<String>() { @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' //RxAndroidcompile 'io.reactivex.rxjava2:rxandroid:2.0.1'compile 'io.reactivex.rxjava2:rxjava:2.0.1'compile 'com.jakewharton.rxbinding2:rxbinding:2.0.0' //Rxjavalifecompile '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'
- Okhttp WebSocket 优化总结
- okhttp进行websocket开发
- okhttp实现websocket长连接
- OkHttp实现分析之Websocket
- OkHttp实现分析之Websocket
- okhttp总结
- OKHttp总结
- Websocket 总结
- WebSocket总结
- WebSocket总结
- 用okhttp实现webSocket长连接
- 使用OkHttp之Websocket实现长连接
- Okhttp的缓存优化
- okhttp使用总结
- OKHttp使用总结
- OkHttp学习总结
- okhttp使用总结
- OkHttp使用总结:
- NLP sentimentic analysis
- GPU虚拟化技术
- D
- 网页授权获取用户基本信息
- 使用nvm后command not found: node
- Okhttp WebSocket 优化总结
- spring学习之---资源访问
- python学习
- javaday02(基本运算符 循环)
- 阿里druid连接池
- js 中的in_array
- 排序 归并
- php mysql PDO 查询操作
- 今天学习HTML