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 功能,可能大家都采用的是第三方(环信,融云 极光等)
但是我们由于这一块的业务目前还是特别大,就自己搭建了聊天和推送系统。
代码中如果有不足的地方,可以相互讨论一下,提供一个我在实际项目中,所遇到困难,解决的思路。

利与弊:

利:

  1. 第三方 集成简单,方便使用,持续有团队优化。
  2. 自己搭建 扩展性高,数据 安全性比较高(提升到https)

弊:

  1. 第三方数据相比自己搭建安全性差一些,所有数据都经过第三方。
  2. 自己搭建开发周期时间长,而且所用到技术需要经过严格测试并且加入特殊异常处理机制, 扩展功能比较麻烦(因为支持功能设计前后端的设计)。

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'