美文网首页Android 网络层
retrofit2调用webservice-3.拦截器,细节优化

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

作者: FrankDaddy | 来源:发表于2017-02-13 15:49 被阅读610次

    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的异常处理)都可以捕获而且去判断,然后做出相应的提示如果,在拦截器里不知道怎么很好的去实现这个。

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

    相关文章

      网友评论

        本文标题:retrofit2调用webservice-3.拦截器,细节优化

        本文链接:https://www.haomeiwen.com/subject/pbxtwttx.html