retrofit2调用webservice-3.拦截器,细节优化

retrofit2调用webservice请求一共3篇
一:工具
二:通过retrofit2 进行soap请求
三:拦截器,通过retrofit2 拦截器拼接入参和过滤出返回值,使soap请求更趋向于http请求

源码地址:https://github.com/Frank1213/retrofit-soap/blob/master/WebServiceSOAP/app/src/main/java/linc/ps/MainActivity.java

先优化下返回值,截取需要的数据,用了Rx的compose关键字:

    /**
     * 从网络请求里面的那串截取出需要的json数据
     * @return
     */
    public static Observable.Transformer<ResponseBody, String> gsonResult(final String papapa) {
        return new Observable.Transformer<ResponseBody, String>() {
            @Override
            public Observable<String> call(Observable<ResponseBody> tObservable) {
                return tObservable.flatMap(
                        new Func1<ResponseBody, Observable<String>>() {
                            @Override
                            public Observable<String> call(ResponseBody response) {
                                try {
                                    if (response == null) {
                                        Log.v("test", "--->请求发生未知错误");
                                        return Observable.error(new ApiException("0002", "接口调用无返回值"));
                                    }
                                    String res = response.string();// 记得要关闭!!!  ResponseBody .string()会自动关闭  .string()和toString()完全不一样!!!
                                    if (res != null && !res.equals("")) {
                                        // 字符转义<-->转义完之后就开始截取数据
                                        String subStr = res.replace("<", "<").replace(">", ">");
                                        // success date like this <GetSysDateTimeResult>string</GetSysDateTimeResult>
                                        String ostar = "<" + papapa + "Result>";
                                        String oend = "</" + papapa + "Result>";
                                        if (subStr.contains(ostar) && subStr.contains(oend)) {
                                            int startIndex = subStr.indexOf(ostar) + ostar.length();
                                            int endtIndex = subStr.lastIndexOf(oend);
                                            String ores = subStr.substring(startIndex, endtIndex);
                                            return returnString(ores);
                                        }
                                    }
                                } catch (Exception e) {
                                    Log.v("test", "--->IOException e: " + e.getMessage());
                                    e.printStackTrace();
                                    return Observable.error(new ApiException("0003", e.getMessage()));
                                }
//                                return Observable.empty();
                                return Observable.error(new ApiException("0001","无"+papapa+"接口信息,请检查调用的接口是否正确"));
                            }
                        }
                );
            }
        };
    }

调用:

    /**
     * 通过省份获取城市代码,截取数据
     */
    public void getSupportCityBySecond() {
        Map map = new HashMap<>();
        map.put("byProvinceName", "福建");
        String result = ApiNode.getParameter("getSupportCity", map);

        AppClient.getInstance().getSupportCity(result)
                .compose(ApiSchedulersHelper.gsonResult("getSupportCity")) // -->获取从数据源获取json
                .subscribeOn(Schedulers.io())// 指定 subscribe() 发生在 IO 线程
                .observeOn(AndroidSchedulers.mainThread())// 指定 Subscriber 的回调发生在主线程
                .subscribe(new RxSubscriber<String>() {
                    @Override
                    public void _onNext(String string) {
                        Log.e("test", "---getSupportCityBySecond _onNext str--->"+string);
                        // <string>福州 (58847)</string><string>厦门 (59134)</string><string>龙岩 (58927)</string><string>南平 (58834)</string><string>宁德 (58846)</string><string>莆田 (58946)</string><string>泉州 (59137)</string><string>三明 (58828)</string><string>漳州 (59126)</string>
                        tv_date.setText(string);
                    }
                    @Override
                    public void _onError(String msg) {
                        Log.d("test", "--->getSupportCityBySecond msg:" + msg);
                        tv_date.setText(msg);
                    }
                });
    }

接下来要换拦截器的车开了

重新撸一个AppSoapClient,初始化retrofit

import com.blankj.utilcode.utils.EncodeUtils;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import linc.ps.net_common.ApiNode;
import linc.ps.net_common.BuildConfig;
import okhttp3.FormBody;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;
import rx.Observable;

/**
 * Retrofit初始化工具,拦截器模式
 */
public class AppSoapClient {
    // 超时时间 默认5秒
    private static final int DEFAULT_TIMEOUT = 5;

    public static Retrofit mRetrofit;
    private ApiSoapStores apiSoapStores;

    private AppSoapClient() {
        if (mRetrofit == null) {
            OkHttpClient.Builder builder = new OkHttpClient.Builder();
            builder.connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS);

            // 日志信息拦截器
            HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
            loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
            //设置 Debug Log 模式
            builder.addInterceptor(loggingInterceptor);

            //SOAP请求过滤器
            HttpRequestInterceptor httpRequestInterceptor = new HttpRequestInterceptor();
            builder.addInterceptor(httpRequestInterceptor);


            OkHttpClient okHttpClient = builder.build();
            mRetrofit = new Retrofit.Builder()
                    .baseUrl(BuildConfig.API_SERVER_URL)
                    .addConverterFactory(GsonConverterFactory.create())
                    .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                    .client(okHttpClient)
                    .build();
        }
        apiSoapStores = mRetrofit.create(ApiSoapStores.class);
    }

    /**
     * SOAP请求过滤器
     */
    static class HttpRequestInterceptor implements Interceptor {
        @Override
        public Response intercept(Chain chain) throws IOException {
            //获取原Resuqest
            Request originalRequest = chain.request();
            //解析出namespace
            String url = originalRequest.url().toString();
            String namespace = url.replace(BuildConfig.API_SERVER_URL + ApiSoapStores.URL_HEAD, "");
            RequestBody requestBody = originalRequest.body();
            if(requestBody != null) {
                //构建新的ResuqestBody
                RequestBody newRequestBody = buildRequestBody(namespace, requestBody);
                if(newRequestBody != null) {
                    //构建新的Request
                    Request.Builder builder = originalRequest.newBuilder();
                    Request request = builder
                            .url(url.replace("/" + namespace, ""))
                            .addHeader("Content-type", ApiSoapStores.CONTENT_TYPE)
                            .addHeader("SOAPAction", ApiSoapStores.SOAP_ACTION_HEAD + namespace)
                            .post(newRequestBody)
                            .build();
                    //开始进行网络请求
                    Response originalResponse = chain.proceed(request);
                    if(originalResponse != null) {
                        //获取原ResponseBody
                        ResponseBody responseBody = originalResponse.body();
                        //构建新的ResponseBody
                        ResponseBody newResponseBody = parseResponseBody(namespace, responseBody);
                        if (newResponseBody != null) {
                            Response.Builder responseBuilder = originalResponse.newBuilder();
                            //返回新的Resonse
                            return responseBuilder.body(newResponseBody).build();
                        }
                    }
                    return originalResponse;
                }
            }
            return chain.proceed(originalRequest);
        }

        /**
         * 入参数据处理
         * @param namespace
         * @param requestBody
         * @return
         */
        private RequestBody buildRequestBody(String namespace, RequestBody requestBody) {

            Map<String, String> map = new HashMap<>();

            if (requestBody instanceof FormBody) {
                Log.e("test", "--->有入参");
                FormBody formBody = (FormBody) requestBody;
                for (int i = 0; i < formBody.size(); i++) {
                    String name = formBody.encodedName(i);
                    String value  = EncodeUtils.urlDecode(formBody.encodedValue(i)).replace("<", "<").replace(">", ">").replace("%24", "$");
//                    String value = formBody.encodedValue(i).replace("<", "<").replace(">", ">").replace("%24", "$");//--->转义字符得封装成一个类
                    map.put(name, value);
                }
                String newBody = ApiNode.getParameter(namespace, map);// 拼接入参的方法
                Log.e("test", "--->有入参-->"+newBody);
                MediaType mediaType = MediaType.parse(ApiSoapStores.CONTENT_TYPE);
                RequestBody newRequestBody = RequestBody.create(mediaType, newBody);
                Log.e("test", "--->有入参-->"+newRequestBody.toString());
                return newRequestBody;
            }else{// 无参的特殊处理,因为转换的FormBody会为空
                Log.e("test", "--->无入参");
                String newBody = ApiNode.getParameter(namespace, map);
                MediaType mediaType = MediaType.parse(ApiSoapStores.CONTENT_TYPE);
                RequestBody newRequestBody = RequestBody.create(mediaType, newBody);
                Log.e("test", "--->无入参-->"+newRequestBody.toString());
                return newRequestBody;
            }
        }
        /**
         * 解析Response
         * @param namespace
         * @param responseBody
         * @return
         */
        private ResponseBody parseResponseBody(String namespace, ResponseBody responseBody) {
            try {

                String res = responseBody.string();
                MediaType mediaType = MediaType.parse(ApiSoapStores.CONTENT_TYPE);
                // 获取到的数据-->截取
                if (res != null && !res.equals("")) {
                    // 字符转义
                    String ostar = "<" + namespace + "Result>";
                    String oend = "</" + namespace + "Result>";
                    if (res.contains(ostar) && res.contains(oend)) {
                        int startIndex = res.indexOf(ostar) + ostar.length();
                        int endIndex = res.lastIndexOf(oend);
                        String ores = res.substring(startIndex, endIndex);
                        return ResponseBody.create(mediaType, ores);
                    }
                }else{
                    return ResponseBody.create(mediaType, res);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    }


    //在访问HttpMethods时创建单例
    private static class SingletonHolder{
        private static final AppSoapClient INSTANCE = new AppSoapClient();
    }

    //获取单例
    public static AppSoapClient getInstance(){
        return AppSoapClient.SingletonHolder.INSTANCE;
    }



    public Observable<ResponseBody> getSupportCity(String byProvinceName){
        return apiSoapStores.getSupportCity(byProvinceName);
    }
}

retrofit的接口类ApiSoapStores

import okhttp3.ResponseBody;
import retrofit2.http.Field;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.POST;
import rx.Observable;

/**
 * Created by Frank on 2016/12/9.
 * (请求有用拦截器)
 * 无入参的要去掉@FormUrlEncoded
 */
public interface ApiSoapStores {
    /**  head基础参数 **/
    String CONTENT_TYPE = "text/xml; charset=utf-8";
    String SOAP_ACTION_HEAD = "http://WebXml.com.cn/";
    String URL_HEAD = "WeatherWebService.asmx/";

    //**  带参请求   **//*
    @FormUrlEncoded
    @POST("WeatherWebService.asmx/getSupportCity")
    Observable<ResponseBody> getSupportCity(@Field("byProvinceName") String byProvinceName);
    /**  无参请求 **/
    @POST("WeatherWebService.asmx/getSupportCity")
    Observable<ResponseBody> getSupportCity();
}

无入参的要去掉@FormUrlEncoded
无入参的要去掉@FormUrlEncoded
无入参的要去掉@FormUrlEncoded
重要的事情说三遍

调用:

    /**
     * 通过省份获取城市代码,截取数据,中文乱码de解决的方案-->入参赋值之前手动转换一次
     */
    public void getSupportCityByThrid() {
        AppSoapClient.getInstance().getSupportCity(et_cityname.getText().toString())
                .subscribeOn(Schedulers.io())// 指定 subscribe() 发生在 IO 线程
                .observeOn(AndroidSchedulers.mainThread())// 指定 Subscriber 的回调发生在主线程
                .subscribe(new Subscriber<ResponseBody>() {
                    @Override
                    public void onCompleted() {
                        Log.e("test", "---getSupportCityByThrid onCompleted--->");
                    }

                    @Override
                    public void onError(Throwable e) {
                        Log.e("test", "---getSupportCityByThrid onError--->"+e.getMessage());
                    }

                    @Override
                    public void onNext(ResponseBody response) {
                        Log.e("test", "---getSupportCityByThrid onNext--->");
                        try {
                            String res = response.string();
                            Log.e("test", "---getSupportCityByThrid onNext str--->"+res);
                            tv_date.setText(res);
                        } catch (IOException e) {
                            Log.e("test", "---getSupportCityByThrid onNext str-IOException-->"+e.getMessage());
                            e.printStackTrace();
                        }
                    }
                });
    }

上面的和原来没用拦截器的几点说明一下:

1.接口会比较清晰
原来:

    // 通过省份获取城市代码,头文件在第一篇都有讲到
    @Headers({
            "Content-Type:text/xml; charset=utf-8",
            "SOAPAction:http://WebXml.com.cn/getSupportCity"
    })
    // 这里对应 http://www.webxml.com.cn/WebServices/WeatherWebService.asmx?op=getWeatherbyCityName 里面的WeatherWebService
    @POST("WeatherWebService.asmx")
    Observable<ResponseBody> getSupportCity(@retrofit2.http.Body String s);

现在:

    @FormUrlEncoded
    @POST("WeatherWebService.asmx/getSupportCity")
    Observable<ResponseBody> getSupportCity(@Field("byProvinceName") String byProvinceName);

2.是入参更清晰
3.返回值不用在每个请求里面特殊处理去截取数据
原来:

        Map map = new HashMap<>();
        map.put("byProvinceName", "福建");
        String result = ApiNode.getParameter("getSupportCity", map);
        AppClient.getInstance().getSupportCity(result)
                .compose(ApiSchedulersHelper.gsonResult("getSupportCity")) // -->获取从数据源获取json
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new RxSubscriber<String>() {
                    @Override
                    public void _onNext(String string) {
                        tv_date.setText(string);
                    }
                    @Override
                    public void _onError(String msg) {
                        tv_date.setText(msg);
                    }
                });

现在:

AppSoapClient.getInstance().getSupportCity(et_cityname.getText().toString())
                .subscribeOn(Schedulers.io())// 指定 subscribe() 发生在 IO 线程
                .observeOn(AndroidSchedulers.mainThread())// 指定 Subscriber 的回调发生在主线程
                .subscribe(new Subscriber<ResponseBody>() {
                    @Override
                    public void onCompleted() {
                    }

                    @Override
                    public void onError(Throwable e) {
                    }

                    @Override
                    public void onNext(ResponseBody response) {
                        try {
                            String res = response.string();
                            tv_date.setText(res);
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                });

因为拼接入参,截取返回值的都在拦截器里做了

但是我这样写会遇到两个问题,现在也还没有很完美的处理方法:
1.入参中文乱码的处理
拦截器获取到的入参,手动再转了一次

Paste_Image.png

转换的方法是调用的网上别人写的
https://github.com/Blankj/AndroidUtilCode/blob/master/README-CN.md
里面的EncodeUtils.urlDecode,但是用起来还是感觉有点奇怪,像请求接口那边加@Headers,入参改成@Field(value = "byProvinceName", encoded=true) String byProvinceName,这些测试了没有效果。

2.现在的项目都是Rx去继承重写了Subscriber,所以异常情况(webservice接口不存在,或者http的异常处理)都可以捕获而且去判断,然后做出相应的提示如果,在拦截器里不知道怎么很好的去实现这个。

以上问题有了解的麻烦告知一下,非常感谢

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345

推荐阅读更多精彩内容