精华内容
下载资源
问答
  • 基于OKHttpwebsocket封装使用

    千次阅读 2019-11-22 20:16:14
    源码解析2.1基础封装2.2使用3.相关知识点3.1 websocket协议3.1.1、客户端:申请协议升级3.1.2、服务端:响应协议升级3.1.3、数据帧3.2 返回数据 Demo源码请点击 1.背景 一般使用到websocket协议的应用场景都是持续...


    Demo源码请点击(网络库中websocket部分)

    1.背景

    一般使用到websocket协议的应用场景都是持续保持长连接,直到业务处理完毕,不再需要保持连接时,则close掉连接。那么官方给出的指导使用文档足咦。我近期工作上接到的任务是,通过websocket协议,流式接收数据。一次任务结束后要求关掉连接,下次任务再重新建立连接。要求减少服务器并发连接数,忽略建立连接的资源消耗。所以本篇文章是介绍再实例化连接的client之后,不关闭分发执行service的前提下,每次都新建websocket连接。整个源码放在了我封装的网络请求框架里。

    2.源码解析

    2.1基础封装

    public class WebSocketClient {
        private Request request;
        private OkHttpClient client;
        private WebSocket webSocket;
    
        public WebSocketClient() {
            client = new OkHttpClient();
            request = new Request.Builder()
                    .url("ws://echo.websocket.org")
                    .build();
        }
    
        public OkHttpClient getClient() {
            return client;
        }
    
        public void start(WebSocketListener listener) {
            client.dispatcher().cancelAll();
            HLogger.d("request id = " + request.toString());
            HLogger.d("listener id = " + listener.toString());
            webSocket = client.newWebSocket(request, listener);
            HLogger.d("webSocket id = " + webSocket.toString());
        }
    
        public void close() {
            if (webSocket != null) {
                webSocket.close(1000, null);
            }
            client.dispatcher().executorService().shutdown();
        }
    }
    

    以上是对Request、OkHttpClient、WebSocket的封装。基本保证只实例化一份Request、OkHttpClient,每次新任务newWebSocket。回调listener可以复用。

    2.2使用

    	HsjWebSocketListener listener = new HsjWebSocketListener();
        StringBuilder stringBuilder = new StringBuilder();
        
        public void initSocket(View view) {
            webSocketClient = new WebSocketClient();
        }
        
        public void webSocket(View view) {
            if (webSocketClient == null) {
                Toast.makeText(this, "请初始化Socket", Toast.LENGTH_SHORT).show();
                return;
            }
            stringBuilder.setLength(0);
            stringBuilder.append(System.currentTimeMillis() + "-onClick\n");
            output();
            webSocketClient.start(listener);
        }
    
        public void closeSocket(View view) {
            webSocketClient.close();
        }
        
        private class HsjWebSocketListener extends WebSocketListener {
            @Override
            public void onOpen(@NotNull WebSocket webSocket, @NotNull Response response) {
                stringBuilder.append(System.currentTimeMillis() + "\n");
                webSocket.send("hello world");
                webSocket.send("welcome");
                webSocket.send(ByteString.decodeHex("adef"));
                webSocket.close(1000, "再见");
            }
    
            @Override
            public void onMessage(@NotNull WebSocket webSocket, @NotNull String text) {
                stringBuilder.append(System.currentTimeMillis() + "-onMessage: " + text + "\n");
                output();
            }
    
            @Override
            public void onMessage(@NotNull WebSocket webSocket, @NotNull ByteString bytes) {
                stringBuilder.append(System.currentTimeMillis() + "-onMessage byteString: " + bytes + "\n");
                output();
            }
    
            @Override
            public void onFailure(@NotNull WebSocket webSocket, @NotNull Throwable t, @Nullable Response response) {
                HLogger.d("onFailure: " + t.getMessage());
                stringBuilder.append(System.currentTimeMillis() + "-onFailure: " + t.getMessage() + "\n");
                output();
            }
    
            @Override
            public void onClosing(@NotNull WebSocket webSocket, int code, @NotNull String reason) {
                stringBuilder.append(System.currentTimeMillis() + "-onClosing: " + code + "/" + reason + "\n");
                output();
            }
    
            @Override
            public void onClosed(@NotNull WebSocket webSocket, int code, @NotNull String reason) {
                stringBuilder.append(System.currentTimeMillis() + "-onClosed: " + code + "/" + reason + "\n");
                output();
            }
        }
    
        private void output() {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    textView.setText(stringBuilder.toString());
                }
            });
        }
    
    1. 先实现一个WebSocketListener,并声明实例化它listener。这是准备工作。
    2. 实例化WebSocketClient。
    3. 任务开始调用webSocketClient.start(listener);
    4. 若再一个新任务,再次调用webSocketClient.start(listener);
    5. 若所有任务结束,页面关闭等情况。调用webSocketClient.close();关闭client。
    6. 单次任务,一般会在onMessage方法中返回是否是最后一条数据信息的标记,若是本次连接的最后一条信息返回。可以主动webSocket.close(1000, “再见”);这个方法只是关闭掉本次连接的websocket。

    3.相关知识点

    3.1 websocket协议

    websocket协议是基于http协议的升级。也就是说客户端先与服务器端通过http协议几次握手建立连接。连接建立后okhttp中封装的websocket框架中,WebSocketListener的onOpen会被回调,即此时已经建立好长连接,可以进行具体业务通信了。

    3.1.1、客户端:申请协议升级

    建立连接时的通信协议采用的是标准的HTTP报文格式,且只支持GET方法。

    GET / HTTP/1.1
    Host: localhost:8080
    Origin: http://127.0.0.1:3000
    Connection: Upgrade
    Upgrade: websocket
    Sec-WebSocket-Version: 13
    Sec-WebSocket-Key: w4v7O6xFTi36lq3RNcgctw==
    

    重点请求首部意义如下:

    • Connection: Upgrade:表示要升级协议
    • Upgrade: websocket:表示要升级到websocket协议。
    • Sec-WebSocket-Version: 13:表示websocket的版本。如果服务端不支持该版本,需要返回一个Sec-WebSocket-Versionheader,里面包含服务端支持的版本号。
    • Sec-WebSocket-Key:与后面服务端响应首部的Sec-WebSocket-Accept是配套的,提供基本的防护,比如恶意的连接,或者无意的连接。

    注意,上面请求省略了部分非重点请求首部。由于是标准的HTTP请求,类似Host、Origin、Cookie等请求首部会照常发送。在握手阶段,可以通过相关请求首部进行 安全限制、权限校验等。

    3.1.2、服务端:响应协议升级

    服务端返回内容如下,状态代码101表示协议切换。到此完成协议升级,协议升级完成后,后续的数据交换则遵照WebSocket的协议。

    HTTP/1.1 101 Switching Protocols
    Connection:Upgrade
    Upgrade: websocket
    Sec-WebSocket-Accept: Oy4NRAQ13jhfONC7bP8dTKb4PTU=
    

    备注:每个header都以\r\n结尾,并且最后一行加上一个额外的空行\r\n。此外,服务端回应的HTTP状态码只能在握手阶段使用。过了握手阶段后,就只能采用特定的错误码。

    3.1.3、数据帧

    webscoket协议中客户端、服务器端通信是以数据帧的格式,非http协议中文本信息格式。
    WebSocket客户端、服务端通信的最小单位是帧(frame),由1个或多个帧组成一条完整的消息(message)。

    发送端:将消息切割成多个帧,并发送给服务端;
    接收端:接收消息帧,并将关联的帧重新组装成完整的消息;

    数据帧格式概览

     0                   1                   2                   3
      0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
     +-+-+-+-+-------+-+-------------+-------------------------------+
     |F|R|R|R| opcode|M| Payload len |    Extended payload length    |
     |I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
     |N|V|V|V|       |S|             |   (if payload len==126/127)   |
     | |1|2|3|       |K|             |                               |
     +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
     |     Extended payload length continued, if payload len == 127  |
     + - - - - - - - - - - - - - - - +-------------------------------+
     |                               |Masking-key, if MASK set to 1  |
     +-------------------------------+-------------------------------+
     | Masking-key (continued)       |          Payload Data         |
     +-------------------------------- - - - - - - - - - - - - - - - +
     :                     Payload Data continued ...                :
     + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
     |                     Payload Data continued ...                |
     +---------------------------------------------------------------+
    

    具体对数据帧的解读,各位可以另行搜索相关文章。

    3.2 返回数据

    websocket回调的WebSocketListener中,各方法返回的数据都在非主线程中。所以若有界面展示需要更新UI的需求,需要通过handler或者runOnUiThread抛到主线程中更新。

    Demo源码请点击(网络库中websocket部分)

    展开全文
  • WebSocketUtil类 public class WebSocketUtil { private String mSocketUrl = ""; private static WebSocketUtil mWebSocketUtil... private WebSocket mWebSocket; private Context mContext; private final int

    WebSocketUtil类

    public class WebSocketUtil {
    
        private String mSocketUrl = "";
        private static WebSocketUtil mWebSocketUtil;
        private OkHttpClient mOkHttpClient;
        private WebSocket mWebSocket;
        private Context mContext;
    
        private final int CLOSE_CODE = 1000;//关闭状态吗
        private final int HEART_TEST = 1;//心跳链接测试
        private final int RESTART_CONNECT = 2;//重连
    
        private final int CONNECT_TIME_OUT = 5 * 1000;//重连时间
        private final int HEART_TEST_TIME = 10 * 1000;//心跳时间
        private boolean isConnect = false;//是否链接成功
        private Handler mHandler = new SocketHandler();
    
        private Map<String, SocketMessageListener> messageListenerMap = new HashMap<>();
    
        public static synchronized WebSocketUtil getWebSocketUtil() {
            if (mWebSocketUtil == null) {
                synchronized (WebSocketUtil.class) {
                    if (mWebSocketUtil == null) {
                        mWebSocketUti
    展开全文
  • WebSocket封装

    2020-09-16 17:07:48
    https://github.com/0xZhangKe/WebSocketDemo郭霖 (RxJava + Okhttp + WebSocket封装 https://blog.csdn.net/u013872857/article/details/80947944 张可 (传统的方式)

    https://github.com/0xZhangKe/WebSocketDemo 郭霖 (RxJava + Okhttp + WebSocket) 封装

    https://blog.csdn.net/u013872857/article/details/80947944  张可 (传统的方式)

    展开全文
  • 基于okhttp3+retrofit2封装的网络库背景接入方式1.导入hsjokhttp module。2.接入使用3.网络库解析 背景 封装的网络库基于okhttp3+retrofit2+rxandroid+rxjava。目的是单独封装处理网络请求,可供项目中多个module...

    Demo及源码地址请点击

    背景

    封装的网络库基于okhttp3+retrofit2+rxandroid+rxjava。目的是单独封装处理网络请求,可供项目中多个module使用,所以Demo中代码模块拆的比较细。包含hsjokhttp(网络库封装)、hsjlogger(日志打印)、bean(对象模块),只需要关注hsjokhttp(网络库封装) 模块,其余两模块都比较简单。库中包含websocket封装部分,详细信息请点击跳转至 基于OKHttp的websocket封装使用

    接入方式

    1.导入hsjokhttp module。

    1. 注意module中gradle文件里,对okhttp等一系列第三方库的依赖不要丢。
    	implementation 'com.squareup.okhttp3:okhttp:4.2.2'
        implementation "io.reactivex.rxjava2:rxjava:2.2.0"
        implementation "io.reactivex.rxjava2:rxandroid:2.1.0"
        implementation 'com.squareup.retrofit2:retrofit:2.6.2'
        implementation 'com.squareup.retrofit2:adapter-rxjava2:2.6.2'
        implementation 'com.squareup.retrofit2:converter-gson:2.6.2'
    

    其中依赖的版本号,可结合自己项目的实际情况更新。日志打印module可以去掉,或者自己调整。我在网络库中依赖它是因为这个logger支持长文本及格式化打印网络请求等信息,查看非常方便。

    1. 因为网络库中封装了CacheInterceptor,这个Interceptor里面有判断网络连接状态,所以需要在module中AndroidManifest.xml文件添加ACCESS_NETWORK_STATE权限。
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.hsj.okhttp" >
        <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    </manifest>
    

    2.接入使用

    1. 在项目的app(主) module的gradle文件中依赖网络库及其他相关库。
    	implementation "io.reactivex.rxjava2:rxjava:2.2.0"
        implementation "io.reactivex.rxjava2:rxandroid:2.1.0"
        implementation 'com.squareup.okhttp3:okhttp:4.2.2'
        implementation 'com.squareup.retrofit2:retrofit:2.6.2'
        implementation 'com.squareup.retrofit2:converter-gson:2.6.2'
        implementation project(path: ':hsjokhttp')
        implementation project(path: ':bean')
        implementation project(path: ':hsjlogger')
    
    1. 新建OkHttpApi接口文件,定义各网络请求接口。
    public interface OkHttpApi {
    
        //get请求方式
        @GET(NetConstants.URL_GET_TOKEN)
        Observable<ResponseBody> getToken();
    
        //获取短信验证码
        @POST("code/sms")
        @FormUrlEncoded
        Observable<ResponseBody> getCodeSms(@FieldMap Map<String, String> bean);
    
        //某维度下的标签
        @POST("story/tag/dimension/notlogin")
        Observable<ResponseBody> getTag(@Body RequestBody bean);
    
        //上传头像图片
        @POST("helper/upload/file/resource")
        Observable<ResponseBody> uploadImgFile(@Body MultipartBody multipartBody);
    }
    
    1. 新建BaseSubscribe文件,主要是基于网络库创建client,生成实例化网络请求的API。具体请看完整示例代码。
    public class BaseSubscribe {
        public static MediaType JSON = MediaType.parse("application/json; charset=utf-8");
        private static final String TOKEN = "";
        private static final String USERID = "";
    
        private static OkHttpApi okHttpApi;
    
        public static OkHttpApi getOkHttpApi() {
            if (okHttpApi==null) {
                okHttpApi = newApi(NetConstants.BASE_URL, getHeaderParams(TOKEN, USERID), OkHttpApi.class);
            }
            return okHttpApi;
        }
    
        /**
         * 关键方法,可以根据需求产出不同的api
         * @param baseUrl
         * @param params
         * @param apiClass
         * @param <T>
         * @return
         */
        public static <T>T newApi(String baseUrl, Map<String, String> params, Class<T> apiClass) {
            NetBuilder builder = new NetBuilder()
                    .setConnectTimeout(HsjNetConstants.DEFAULT_CONNECT_TIMEOUT)
                    .setReadTimeout(HsjNetConstants.DEFAULT_READ_TIMEOUT)
                    .setWriteTimeout(HsjNetConstants.DEFAULT_WRITE_TIMEOUT)
                    .setRetryOnConnectionFailure(true);
            //添加header
            if (params != null && params.size()>0) {
                HeaderInterceptor headerInterceptor = new HeaderInterceptor(params);
                builder.addInterceptor(headerInterceptor);
            }
            //添加缓存
            File cacheFile = new File(App.getAppContext().getExternalCacheDir(), HsjNetConstants.CACHE_NAME);
            Cache cache = new Cache(cacheFile, 1024 * 1024 * 50);
            builder.addCache(cache);
    
            //添加缓存拦截器
            CacheInterceptor cacheInterceptor = new CacheInterceptor(App.getAppContext());
            builder.addInterceptor(cacheInterceptor);
    
    //        builder.addNetInterceptor(new NetInterceptor());
    
            return builder.build(baseUrl).create(apiClass);
        }
    
        private static Map<String, String> getHeaderParams(String token, String userId) {
            String timestamp, signature;
    
            timestamp = String.valueOf(System.currentTimeMillis() / 1000);
    
            Map<String, String> params = new HashMap<>();
            if (!TextUtils.isEmpty(token)) {
                params.put("token", token);
            }
            if (!TextUtils.isEmpty(userId) && !"null".equals(userId)) {
                params.put("userId", userId);
            }
            params.put("nounce", "");
            params.put("timestamp", timestamp);
            signature = "";
            params.put("signature", signature);
    
            return params;
        }
    
        /**
         * 设置订阅 和 所在的线程环境
         * @param o
         * @param s
         */
        public static void toSubscribe(Observable<ResponseBody> o, DisposableObserver<ResponseBody> s) {
            o.subscribeOn(Schedulers.io())
                    .unsubscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread())
                    .retry(1)//请求失败重连次数
                    .subscribe(s);
        }
    

    需要注意的是newApi方法,这个方法里面定义了网络请求中各种参数添加设置。比如连接等超时、通过拦截器方式添加header信息、缓存文件及大小、缓存拦截器、网络拦截器等。这个地方可以根据自己的实际需求设置。
    若有多个api文件,则需要在此注册多个api。
    header信息,我单独封装了一个方法,以Map形式传入拦截器,一般可以在此添加一些signature信息。

    1. 封装HsjSubscribe文件,这个是基于api,定义自己的网络请求方法,一般与api中网络请求接口一一对应。Demo中的示例支持多种请求方式。Get、Post(body为JSON格式、body为Form形式)、文件上传 等示例。
    public class HsjSubscribe {
        /**
         * 获取标签
         * @param subscriber
         */
        public static void getTag(DisposableObserver<ResponseBody> subscriber) {
            Map<String,String> map = new HashMap<>();
            map.put("dimension", "4");
            map.put("language", "zh_CN");
            Observable<ResponseBody> observable = BaseSubscribe.getOkHttpApi().getTag(RequestBody.create(new Gson().toJson(map), BaseSubscribe.JSON));
            BaseSubscribe.toSubscribe(observable, subscriber);
        }
    
        public static void getCodeSms(String mobile, DisposableObserver<ResponseBody> subscriber) {
            Map<String,String> map = new HashMap<>();
            map.put("mobile", mobile);
            Observable<ResponseBody> observable = BaseSubscribe.getOkHttpApi().getCodeSms(map);
            BaseSubscribe.toSubscribe(observable, subscriber);
        }
    
        /**
         * 获取标签
         * @param subscriber
         */
        public static void getToken(DisposableObserver<ResponseBody> subscriber) {
            Observable<ResponseBody> observable = BaseSubscribe.getOkHttpApi().getToken();
            BaseSubscribe.toSubscribe(observable, subscriber);
        }
    
        /**
         * 上传头像接口
         * type 1=image 2=audio
         */
        public static void uploadFileToGetUrl(int type, File file, DisposableObserver<ResponseBody> subscriber) {
            Observable<ResponseBody> observable = BaseSubscribe.getOkHttpApi().uploadImgFile(filesToMultipartBody(file, type));
            BaseSubscribe.toSubscribe(observable, subscriber);
        }
    
        private static MultipartBody filesToMultipartBody(File file, int type) {
            MultipartBody.Builder builder = new MultipartBody.Builder().setType(MultipartBody.FORM);
            RequestBody requestBody = RequestBody.create(file, MediaType.parse(type == 1 ? "image/*" : "audio/*"));
            builder.addFormDataPart("file", file.getName(), requestBody);
            return builder.build();
        }
    }
    
    1. 定义网络请求回调类BaseCallback,此类继承自网路库中NetCallback,已对请求返回回的参数类型做泛型处理。一般一些需要统一处理的逻辑可以在此处理,比如token失效后需要退出登录等。
    public abstract class BaseCallback<T> extends NetCallback<T> {
        /**
         * 统一处理的逻辑,都在类似这样的方法中处理。暴露在每个最外层的callback只处理指定场景下的业务逻辑。
         */
        @Override
        public void logout() {
    
        }
    }
    
    1. 网络请求。
    	public void getToken(View view) {
            HsjLogger.d("getToken");
            HsjSubscribe.getToken(new BaseCallback<Token>() {
                @Override
                public void onSuccess(Token result) {
                    HsjLogger.d("--getToken--onSuccess--");
                }
    
                @Override
                public void onFault(int code, String errorMsg) {
                    HsjLogger.d("--getToken--onFault--");
                }
    
                @Override
                public void netError() {
                    HsjLogger.d("--getToken--netError--");
                }
            });
        }
    
        public void uploadFile(View view) {
            File file = new File(getExternalFilesDir(null) + File.separator + "image/icon_weixin_kefu.png");
            HsjSubscribe.uploadFileToGetUrl(1, file, new BaseCallback<FileUploadResponse>() {
                @Override
                public void onSuccess(FileUploadResponse result) {
                    HsjLogger.d("--upload--onSuccess--");
                }
    
                @Override
                public void onFault(int code, String errorMsg) {
                    HsjLogger.d("--upload--onFault--");
                }
    
                @Override
                public void netError() {
                    HsjLogger.d("--upload--netError--");
                }
            });
        }
    
        public void jsonRequest(View view) {
            HsjSubscribe.getTag(new BaseCallback<List<Tag>>() {
    
                @Override
                public void onSuccess(List<Tag> result) {
                    HsjLogger.d("--onSuccess--");
                }
    
                @Override
                public void onFault(int code, String errorMsg) {
                    HsjLogger.d("--onFault--" + errorMsg);
                }
    
                @Override
                public void netError() {
                    HsjLogger.d("--netError--");
                }
            });
        }
    
        public void formRequest(View view) {
            HsjSubscribe.getCodeSms("13079079998", new BaseCallback<Object>() {
                @Override
                public void onSuccess(Object result) {
                    HsjLogger.d("--login--onSuccess--");
                }
    
                @Override
                public void onFault(int code, String errorMsg) {
                    HsjLogger.d("--login--onFault--");
                }
    
                @Override
                public void netError() {
                    HsjLogger.d("--login--netError--");
                }
            });
        }
    

    以上示例分别是Demo中各类请求示例。如果一个统一的返回体格式是{“success”:true,“code”:“20000”,“message”:“成功”,“data”:{}},目前的封装已处理到data层级,所以直接传入回调需要的类型即可。

    1. 记得在主工程AndroidManifest.xml文件中添加网络请求等权限。
    	<uses-permission android:name="android.permission.INTERNET" />
        <uses-permission android:name="android.permission.READ_PHONE_STATE" />
        <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    

    3.网络库解析

    1. BaseSubscribe类中newApi方法,NetBuilder解析。
    public class NetBuilder {
    
        private Retrofit retrofit;
        private OkHttpClient.Builder okHttpBuilder;
        public NetBuilder() {
            //手动创建一个OkHttpClient并设置超时时间
            okHttpBuilder = new OkHttpClient.Builder();
        }
    
        /**
         * 连接超时
         * @param outTime
         * @return
         */
        public NetBuilder setConnectTimeout(int outTime) {
            okHttpBuilder.connectTimeout(outTime, TimeUnit.SECONDS);
            return this;
        }
    
        /**
         * 读超时
         * @param outTime
         * @return
         */
        public NetBuilder setReadTimeout(int outTime) {
            okHttpBuilder.readTimeout(outTime, TimeUnit.SECONDS);
            return this;
        }
    
        /**
         * 写超时
         * @param outTime
         * @return
         */
        public NetBuilder setWriteTimeout(int outTime) {
            okHttpBuilder.writeTimeout(outTime, TimeUnit.SECONDS);
            return this;
        }
    
        /**
         * 错误重连
         * @param retry
         * @return
         */
        public NetBuilder setRetryOnConnectionFailure(boolean retry) {
            okHttpBuilder.retryOnConnectionFailure(retry);
            return this;
        }
    
        /**
         * 添加拦截器
         * @param interceptor
         * @return
         */
        public NetBuilder addInterceptor(Interceptor interceptor) {
            okHttpBuilder.addInterceptor(interceptor);
            return this;
        }
    
        /**
         * 添加倒数第二层拦截器
         * @param interceptor
         * @return
         */
        public NetBuilder addNetInterceptor(Interceptor interceptor) {
            okHttpBuilder.addNetworkInterceptor(interceptor);
            return this;
        }
    
        /**
         * 添加缓存文件
         * @param cache
         * @return
         */
        public NetBuilder addCache(Cache cache) {
            okHttpBuilder.cache(cache);
            return this;
        }
    
        public Retrofit build(String url) {
            retrofit = new Retrofit.Builder()
                    .client(okHttpBuilder.build())
                    .addConverterFactory(GsonConverterFactory.create())//json转换成JavaBean
                    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                    .baseUrl(url)
                    .build();
            return retrofit;
        }
    }
    

    建造者模式实例化OkHttpClient,并提供各类超时设置方法、添加拦截器、以及实例化Retrofit,都在这个类中。

    1. NetCallback类继承自DisposableObserver。并实现自定义的APICallback。
    public abstract class NetCallback<T> extends DisposableObserver<ResponseBody> implements APICallback<T> {
        /**
         * 处理各种业务逻辑及错误
         * @param body
         */
        @Override
        public void onNext(ResponseBody body) {
            try {
                String result = body.string();
                HsjLogger.longInfo(result);
                JSONObject jsonObject = new JSONObject(result);
                String resultCode = jsonObject.getString("code");
                if ("20000".equals(resultCode)) {
                    Gson gson = new Gson();
                    String data = jsonObject.getString("data");
                    if (!TextUtils.isEmpty(data)) {
                        HsjLogger.longInfo(data);
                        T t = gson.fromJson(data, getType());
                        onSuccess(t);
                    }
                } else if ("00011".equals(resultCode)) {
                    //token过期,需要重新登录
                    logout();
                } else {
                    String errorMsg = jsonObject.getString("message");
                    onFault(0, errorMsg);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 请求出错,包含网络错误
         * @param e
         */
        @Override
        public void onError(Throwable e) {
            HsjLogger.d(e.getMessage());
            netError();
        }
    
        @Override
        public void onComplete() {
    
        }
    
        private final Type getType() {
            /**获取第一个泛型类型*/
            ParameterizedType parameterizedType = (ParameterizedType) getClass().getGenericSuperclass();
            return parameterizedType.getActualTypeArguments()[0];
        }
    }
    
    public interface APICallback<T> {
        void logout();
    
        void onSuccess(T result);
    
        void onFault(int code, String errorMsg);
    
        void netError();
    }
    

    如果是网络错误,包括400、404等等,则会回调onError(Throwable e),所以所有网络错误处理可以放在这里,也可以像logout() 一样,抛回BaseCallback处理。只要是http返回code=200后的业务逻辑处理都会在onNext(ResponseBody body) 中。比如token过期,需要重新登录。

    也可以自己重定义APICallback接口,按照自己的业务逻辑删减接口方法。总之最终网络请求回调的实现方法+BaseCallback类中实现的方法数=DisposableObserver+APICallback类中的方法数

    该库还可以灵活调整,比如多个api,或者多个callback。我们公司的项目经历过几任前后端开发的同学,导致用户中心模块、一般业务服务器接口等baseUrl不一样,请求参数处理方式也不一样,等等。就涉及到需要多个api(一个baseurl=需要一个api),多个callback(返回结果数据格式也不统一)。

    1. 通过Interceptor添加header。
    public class HeaderInterceptor implements Interceptor {
        private Map<String, String> params;
    
        public HeaderInterceptor(Map<String, String> p) {
            this.params = p;
        }
    
        @NotNull
        @Override
        public Response intercept(@NotNull Chain chain) throws IOException {
            Request originalRequest = chain.request();
            //设置头部信息
            Request.Builder requestBuilder = originalRequest.newBuilder();
            if (params != null && params.size() > 0) {
                for (Map.Entry<String, String> entry : params.entrySet()) {
                    String value = entry.getValue();
                    if (TextUtils.isEmpty(value) || "null".equals(value)) {
                        value = "";
                    }
                    requestBuilder.addHeader(entry.getKey(), value);
                }
            }
    
            requestBuilder.method(originalRequest.method(), originalRequest.body());
            Request request = requestBuilder.build();
    
            //打印请求信息,正式项目中可以注释掉
            RequestBody requestBody = request.body();
            String body = null;
            if (requestBody != null) {
                Buffer buffer = new Buffer();
                requestBody.writeTo(buffer);
    
                body = buffer.readString(Charset.forName("UTF-8"));
            }
            HsjLogger.longInfo(String.format("发送请求\nmethod:%s\nurl:%s\nheaders: %sbody:%s",
                    request.method(), request.url(), request.headers(), body));
    
            return chain.proceed(request);
        }
    }
    
    1. 缓存拦截器CacheInterceptor。
    public class CacheInterceptor implements Interceptor {
        private Context mContext;
        public CacheInterceptor(Context context) {
            mContext = context;
        }
    
        @NotNull
        @Override
        public Response intercept(@NotNull Chain chain) throws IOException {
            Request request = chain.request();
            if (!NetUtil.isNetworkConnected(mContext)) {
                request = request.newBuilder()
                        .cacheControl(CacheControl.FORCE_CACHE)
                        .build();
            }
            Response response = chain.proceed(request);
            if (NetUtil.isNetworkConnected(mContext)) {
                int maxAge = 0;
                // 有网络时 设置缓存超时时间0个小时
                response.newBuilder()
                        .header("Cache-Control", "public, max-age=" + maxAge)
                        .removeHeader(HsjNetConstants.CACHE_NAME)// 清除头信息,因为服务器如果不支持,会返回一些干扰信息,不清除下面无法生效
                        .build();
            }  else {
                // 无网络时,设置超时为4周
                int maxStale = 60 * 60 * 24 * 28;
                response.newBuilder()
                        .header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
                        .removeHeader(HsjNetConstants.CACHE_NAME)
                        .build();
            }
            return response;
        }
    }
    
    1. 关于拦截器。
      拦截器时序图
      关于拦截器,我借用网上的一张图,里面描述的非常清晰。okhttp框架共7层拦截器处理,一般用户能自定义第一层和倒数第二层网络处理拦截器。具体的拦截器相关知识,不再这儿做详细阐述。我在封装库中自定义headerInterceptor和CacheInterceptor,都属于第一层拦截器。在demo中我还自定义了一个NetInterceptor。需要注意的是NetInterceptor中,如果对返回结果进行了处理,则在NetCallback回调中接收不到业务数据。即OnNext方法中返回为空。

    2. 返回的业务数据处理逻辑。
      NetCallback类中泛型的使用,让返回的业务数据类型处理方便很多。主要逻辑就是网络请求时,实例化的callback将需要的返回类型传递至NetCallback类。在NetCallback类onNext(ResponseBody body)方法中,通过Gson处理,将返回结果转为目标类型,返回给上层调用类。

    			String result = body.string();
                JSONObject jsonObject = new JSONObject(result);
                String resultCode = jsonObject.getString("code");
                if ("20000".equals(resultCode)) {
                    Gson gson = new Gson();
                    String data = jsonObject.getString("data");
                    if (!TextUtils.isEmpty(data)) {
                        T t = gson.fromJson(data, getType());
                        onSuccess(t);
                    }
                }
    

    getType()方法是获取该回调类中第一个泛型类型。

    	private final Type getType() {
            /**获取第一个泛型类型*/
            ParameterizedType parameterizedType = (ParameterizedType) getClass().getGenericSuperclass();
            return parameterizedType.getActualTypeArguments()[0];
        }
    

    4.结束语

    我会持续更新该网络封装库。并欢迎各位将使用过程中遇到的问题,及使用的不便之处反馈给我,我持续更新在封装库中。也欢迎各位有更好的优化的idea,一并提出。谢谢~

    Demo及源码地址请点击

    展开全文
  • Okhttp实现websocket链接

    2018-05-29 15:20:42
    利用okhttp实现的websocket长链接,里面做了断线重连,我设定的是10秒重连,有需要的可以更改
  • 基于okhttpwebsocket封装,实现长连接,数据回调,断开重连。
  • websocket封装

    2016-12-30 17:47:05
    android开发中,使用okhttpwebsocket,建立稳定可靠的长连接,亲测,没有任何问题,欢迎下载
  • 目前Android WebSocket 框架 主要包括:SocketIOJava-WebSocketOkHttp WebSocket一开始我首选的是采用SocketIO方案,因为考虑该方案封装接口好,提供异步回调机制,但和后端同事沟通发现目前客户端的SocketIO不支持ws...
  • okhttp实现webSocket长连接

    万次阅读 2016-08-24 15:00:22
    因为项目需求极光不能满足...Okhttp支持webSocket协议。 上代码: 在build.gradle 配制 okhttp依赖 compile 'com.squareup.okhttp3:okhttp:3.4.1' 加入网络权限: <uses-permission android:n...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,376
精华内容 550
关键字:

okhttpwebsocket封装