Okhttp WebSocket 优化总结

开场白: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
 * 邮箱:[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'

你可能感兴趣的:(android_开发)