Retrofit的封装优化

2018-10-10  本文已影响0人  饭勺

一、前言

Retrofit,是针对OKhttp的二次封装,使用在网络请求方面。目前已比较普及。本文通过对Retrofit的封装,让Http请求模板化并且丰富其功能。

二、功能

封装后,功能特性如下:
1.支持网络请求和Activity生命周期绑定;
2.支持主动取消一个请求;
3.支持回调函数的线程选择;
4.支持超时时间配置
5.支持缓存策略配置(包括离线缓存和在线缓存);
6.支持多文件上传和图文上传。
7.上层接口支持多种数据类型设置。

三、使用介绍

1.Http初始化,建议放在Application中:
  HttpClient.getInstance().init(context);

2.Builder类介绍
  1) CommonBuilder,普通Builder
  2) LifeCycleBuilder,继承自CommonBuilder,提供生命周期管理的Builder。
  3) UploadBuilder,继承自LifeCycleBuilder,文件上传Builder.

3.Http请求流程和说明

  1) 针对每一个url的请求,在对应的请求结果Bean中构建对应Builder。示例如下,在AccountExistResponse构建Builder

  public class AccountExistResponse {

    public String code;
    public String data;
    public String message;

    @Override
    public String toString() {
        return  "code = " + code +
                "; data = " + data +
                "; message = " + message;
    }

    public static class Builder extends CommonBuilder<AccountExistResponse> {
       @Override
        protected String getPath() {
            return "checkAccountIfExist";
       }

        @Override
        protected String getBaseUrl() {
            return "https://61.141.236.30:443/api/register/";
        }

        @Override
        protected String getMethod() {
            return HttpMethod.POST;
        }
    }
  }

  2) 构建参数并发起请求

    HashMap<String, String> map = new HashMap();
    map.put("phone", "15820463555");

    new AccountExistResponse.Builder().addBodyMap(map)
            .build(new HttpCallBack<AccountExistResponse>() {
                @Override
                public void onSuccess(AccountExistResponse accountExistResponse) {
                    Toast.makeText(HttpDemoActivity.this, "请求成功!  " + accountExistResponse.toString(), Toast.LENGTH_LONG).show();
                    Log.d(TAG, accountExistResponse.toString());
                }

                @Override
                public void onError(int stateCode, String errorInfo) {
                    Toast.makeText(HttpDemoActivity.this, "请求失败!  stateCode = " + stateCode + "; errorInfo = " +errorInfo, Toast.LENGTH_LONG).show();
                    Log.d(TAG, "stateCode = " + stateCode + "; errorInfo = " +errorInfo);
                }
            });

  3) 默认配置中有超时配置,自定义操作如下:

    HttpConfig httpConfig = HttpConfig.create(false);
    httpConfig.connectTimeout(5);
    httpConfig.readTimeout(5);
    httpConfig.writeTimeout(5);
    new RouteInfoResponse.Builder().addBodyMap(map)
            .addHeader("name", "wsh")
            .setHttpCustomConfig(httpConfig)
            .build(new HttpCallBack<RouteInfoResponse>() {
                @Override
                public void onSuccess(RouteInfoResponse RouteInfoResponse) {
                    Toast.makeText(HttpDemoActivity.this, "请求成功!  " + RouteInfoResponse.toString(), Toast.LENGTH_LONG).show();
                    Log.d(TAG, RouteInfoResponse.toString());
                }

                @Override
                public void onError(int stateCode, String errorInfo) {
                    Toast.makeText(HttpDemoActivity.this, "请求失败!  stateCode = " + stateCode + "; errorInfo = " +errorInfo, Toast.LENGTH_LONG).show();
                    Log.d(TAG, "stateCode = " + stateCode + "; errorInfo = " +errorInfo);
                }
            });

    a.  false 表示:无需Header中默认的配置,注意超时配置不属于Header配置; true表示保留默认的Header信息;
    b.  缓存策略也在HttpConfig中配置

  4) 生命周期绑定

    new WeatherResponse.Builder().setTag(HttpDemoActivity.this) .addParamsMap(map)
    //setTag(HttpDemoActivity.this) 即表示此请求会在HttpDemoActivity销毁时自动断开

  5) 回调线程设置,根据build的重载方法设置

    new RouteInfoResponse.Builder().addBodyMap(map)
            .addHeader("name", "wsh")
            .build(true, new HttpCallBack<RouteInfoResponse>()
    //ture 表示在主线程中返回,默认也是主线程,false即为在子线程中返回

  6) 主动取消请求:保存Builder,调用builder.dispose();

四、主要实现流程:HttpClient类的分析

1.HttpClient为单例
2.提供init方法传入context
3.提供refresh方法更新Retrofit对象以及OkHttpClient对象

/**
 * @param url
 * @param headerConfig
 * @return
 */
public HttpClient refresh(String url, HttpConfig headerConfig, HttpCallBack<T> callback) {

    //获取缓存Retrofit
    if (TextUtils.isEmpty(url)) {
        return null;
    }

    int key = getHashKey(url, headerConfig);
    mCurrentRetrofit = getCacheRetrofit(key);
    if (mCurrentRetrofit != null) {
        mCurrentServices = mCurrentRetrofit.create(HttpServices.class);
        return this;
    }
    OkHttpClient client = getOkHttpClient(url, headerConfig, callback);
    try {
        mCurrentRetrofit = new Retrofit.Builder()
                .client(client)
                .addConverterFactory(new ConvertFactory(mGson))
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .baseUrl(url)
                .build();

    } catch (Exception e) {
        mCurrentRetrofit = null;
        e.printStackTrace();
        return null;
    }

    mCurrentServices = mCurrentRetrofit.create(HttpServices.class);
    putCacheRetrofit(key, mCurrentRetrofit);
    return this;
}

A.Retrofit对象针对key值有缓存,缓存大小为100;
B.Key值的生成由url和header信息共同决定

private OkHttpClient getOkHttpClient(String url, HttpConfig headerConfig, final HttpCallBack<T> callback) {
    if (mContext == null) {
        return null;
    }
    if (headerConfig == null) {
        headerConfig = HttpConfig.create(true);

    }
    final HttpConfig config = headerConfig;
    //缓存容量

    //缓存路径
    String cacheFile = mContext.getCacheDir() + "/retrofit";
    Cache cache = new Cache(new File(cacheFile), HttpConstants.SIZE_OF_CACHE);
    OkHttpClient.Builder builder = new OkHttpClient.Builder()
            .connectTimeout(config.getConnectTimeout(), TimeUnit.SECONDS)
            .readTimeout(config.getReadTimeout(), TimeUnit.SECONDS)
            .writeTimeout(config.getWriteTimeout(), TimeUnit.SECONDS)
            .addInterceptor(getHttpInterceptor(headerConfig, callback))
            .cache(cache);

    //测试用  跳过所有认证
    if (url.startsWith(HttpConstants.HTTPS)) {
        //SSLSocketFactory sslSocketFactory = new SslContextFactory().getSslSocket(mContext).getSocketFactory();
        //builder.sslSocketFactory(sslSocketFactory);
        builder.sslSocketFactory(new SslContextFactory().createSSLSocketFactory())
                .hostnameVerifier(new HostnameVerifier() {
            @Override
            public boolean verify(String hostname, SSLSession session) {
                return true;
            }
        });
    }

    return builder.build();
}

A.针对Https的请求需要加载认证证书,目前没有则跳过所有认证

4.通过getHttpInterceptor方法获取拦截器

private Interceptor getHttpInterceptor(final HttpConfig config, final HttpCallBack<T> callback) {
    Interceptor interceptor = new Interceptor() {
        @Override
        public okhttp3.Response intercept(Chain chain) throws IOException {
            Request okHttpRequest = chain.request();
            RequestBody body = okHttpRequest.body();
            if (body != null) {
                Log.i(TAG, "upload intercept: " + body.getClass().getSimpleName());
                ProgressRequestBody prb = new ProgressRequestBody(body);
                prb.setProgressListener(callback);
                Request.Builder builder = okHttpRequest.newBuilder().method(okHttpRequest.method(), prb);
                if (!config.getHeaders().isEmpty()) {
                    Set<Map.Entry<String, String>> entrySet = config.getHeaders().entrySet();
                    for (Map.Entry<String, String> entry : entrySet) {
                        builder.addHeader(entry.getKey(), entry.getValue());
                    }
                }

                boolean networkAvailable = NetWorkUtils.isNetworkAvailable(mContext);
                if (networkAvailable && config.isNeedNetWorkCache()) { //有网络连接,看是否有配置,没配置,则走默认不缓存
                    CacheControl cacheControl = new CacheControl.Builder()
                            .maxAge(config.getNetWorkCacheTimeout(), TimeUnit.SECONDS)
                            .build();

                    builder.addHeader(HttpConstants.CACHE_CONTROL, cacheControl.toString());
                } else if (!networkAvailable && config.isNeedNoNetWorkCache()) { //离线缓存
                    CacheControl cacheControl = new CacheControl.Builder()
                            .maxAge(config.getNoNetWorkCacheTimeout(), TimeUnit.SECONDS)
                            .build();

                    builder.addHeader(HttpConstants.CACHE_CONTROL, cacheControl.toString());
                }
                okHttpRequest = builder.build();
            }
            return chain.proceed(okHttpRequest);
        }
    };
    return interceptor;
}

A. 拦截器中增加Header信息,网络缓存配置,还有设置进度监听等操作。

5.真正的请求方法doSubscribe()

private void doSubscribe(final String pathKey, final int tagHash,
                         Observable<Response<String>> observable,
                         final boolean onUiCallBack, final HttpCallBack<T> callback) {

    Observable<Pair<String, T>> mapObservable =
            observable.map(new Function<Response<String>, Pair<String, T>>() {
        @Override
        public Pair<String, T> apply(retrofit2.Response<String> response) throws Exception {
            String msg = "";
            Pair<String, T> pair;
            if(response.isSuccessful()){
                String data = response.body();
                if (data != null) {
                    Class<T> cls = getParameterizedTypeClass(callback);
                    T t = mGson.fromJson(data, cls);
                    if (t != null) {
                        pair = new Pair<>(data, t);
                    } else {
                        msg = "json parse fail";
                        pair = new Pair<>(msg, null);
                    }
                    if (!onUiCallBack && t != null){ //子线程返回
                        callback.onSuccess(t);
                    }else if (!onUiCallBack && t == null){
                        callback.onError(HttpStateCode.ERROR_SUBSCRIBE_ERROR, msg);
                    }
                    return pair;
                } else {
                    msg = "response body is null";
                }
            }else {
                ResponseBody errorBody = response.errorBody();
                if (errorBody == null) {
                    msg = "errorBody is null";
                } else {
                    msg = errorBody.string();
                }
            }

            Log.e(TAG, "apply: " + msg);
            pair = new Pair<>(msg, null);
            if (!onUiCallBack){
                callback.onError(HttpStateCode.ERROR_SUBSCRIBE_ERROR, msg);
            }
            return pair;
        }
    });

    mapObservable.subscribeOn(Schedulers.io())
            .unsubscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(new Observer<Pair<String, T>>() {

                @Override
                public void onSubscribe(Disposable disposable) {
                    cacheDisposableIfNeed(disposable, tagHash, pathKey);
                }

                @Override
                public void onNext(Pair<String, T> pair) {
                    if (!onUiCallBack) {
                        return;
                    }
                    T t = pair.second;
                    if (t != null) {
                        callback.onSuccess(t);
                    } else {
                        callback.onError(HttpStateCode.ERROR_ONNEXT_NULL, null);
                    }
                }

                @Override
                public void onError(Throwable throwable) {
                    dispose(tagHash, pathKey);
                    if (onUiCallBack) {
                        callback.onError(HttpStateCode.ERROR_SUBSCRIBE_ERROR, throwable == null
                                ? "" : throwable.getMessage());
                    }
                }

                @Override
                public void onComplete() {
                    dispose(tagHash, pathKey);
                }
            });
}

A. 将(retrofit2.Response<String> response)转化为Pair<String, T> ,T即为返回信息的java Bean类;
B. 转为Java Bean类是从callback中的泛型获取到T的class对象,在用Gson转化
    private Class<T> getParameterizedTypeClass(Object obj) {
        ParameterizedType pt = (ParameterizedType) obj.getClass().getGenericSuperclass();
        Type[] atr = pt.getActualTypeArguments();
        if (atr != null && atr.length > 0) {
            return (Class<T>) atr[0];
        }
        return null;
    }
C.控制返回结果的线程

6.缓存Disposable以便取消请求

/**
 * 缓存Disposable
 *
 * @param disposable
 * @param hash
 * @param key
 */
private void cacheDisposableIfNeed(Disposable disposable, int hash, String key) {
    if (disposable == null) {
        return;
    }
    Pair<Integer, Disposable> pair = Pair.create(key.hashCode(), disposable);
    List<Pair<Integer, Disposable>> list = mDisposableCache.get(hash);
    if (list == null) {
        list = new ArrayList<>();
    }
    list.add(pair);
    mDisposableCache.put(hash, list);
}

7.取消请求的实现

/**
 * @param tag 请求时传入的tag
 */
public void cancel(Object tag) {
    if (tag == null) return;
    List<Pair<Integer, Disposable>> disposableList;
    disposableList = mDisposableCache.get(tag.hashCode());
    if (disposableList != null) {
        for (Pair<Integer, Disposable> pair : disposableList) {
            pair.second.dispose();
        }
        mDisposableCache.remove(tag.hashCode());
    }
}

/**
 *
 * @param hash
 * @param key
 * @return 返回是否成功删除
 */
public boolean dispose(int hash, @NonNull String key) {
    List<Pair<Integer, Disposable>> list = mDisposableCache.get(hash);
    Pair<Integer, Disposable> removePair = null;
    if (list != null) {
        for (Pair<Integer, Disposable> pair : list) {
            if (key.hashCode() == pair.first) {
                pair.second.dispose();
                removePair = pair;
                break;
            }
        }
    }
    if (list != null && removePair != null) {
        list.remove(removePair);
        return true;
    }
    return false;
}

8.绑定生命周期

public class HttpClient<T> implements GenericLifecycleObserver {
    ......
    @Override
    public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
        if (source.getLifecycle().getCurrentState() == Lifecycle.State.DESTROYED) {
            source.getLifecycle().removeObserver(this);
            cancel(source);
        }
    }
    ......
}

至此,整个封装思路便讲完了。以下是源码路径:

源码及Demo地址点这里

上一篇下一篇

猜你喜欢

热点阅读