美文网首页鸿蒙tool
鸿蒙开发之网络框架搭建,MVP+Retrofit2+okhttp

鸿蒙开发之网络框架搭建,MVP+Retrofit2+okhttp

作者: 打酱油的日光灯 | 来源:发表于2021-01-27 19:59 被阅读0次

    抓住人生中的一分一秒,胜过虚度中的一月一年!

    小做个动图开篇引题


    懒洋洋.gif

    鸿蒙背景

    2020年9月10号,鸿蒙2.0(HarmonyOS 2.0)系统正式发布,鸿蒙2.0面向应用开发者发布Beta版本,在2020年9月10发布大屏,手表,车机版鸿蒙,2020年12月发布手机版鸿蒙。在2020年9月10日,鸿蒙开源路标面向内存128KB-128MB终端设备;2021年10月,将面向4GB以上所有设备。

    前言

    作为一个安卓开发者,能够看到属于国产的操作系统确实很兴奋,也许将来的某一天可能和android一战,但实际踩坑中发现,鸿蒙需要走的路很长,系统优化方面还有很多,和android差距还是特别巨大的,入坑鸿蒙开发,可参考的东西少之有少,几乎为0,所以需要大家一起行动起来,互相分享,生态圈才能形成,给大家分享点自己的踩坑之路和成果。

    一个APP的必须品肯定是网络访问,所以第一篇文章先搭建个网络框架供大家参考,可以更快的入手鸿蒙开发,鸿蒙支持java开发,所以选择了Retrofit+okhttp组合,下面给大家演示下如何封装使用,RxAndroid不可用,需要改装成RxHarmony,有人肯定想为何不封装携程+mvvm,这个需要问官方是否支持

    • QQ群 : 鸿蒙开发技术讨论QQ群:1084365075


    相关业务需求及解决方案
    一、 MVP+Retrofit2+okhttp3+Rxjava2+RxHarmony框架基本搭建及使用
    二、 BaseAbilitySliceBaseFraction封装,搭配框架使用
    三、 Retrofit运行时动态改变BaseUrl解决方案,及动态改变retrofit.create(cls)的接口cls,来实现组件化思想如androidArouter,和鸿蒙服务的理念可分可合可流转多entry包思想
    四、 RetrofitGson解析,请求返回的类型不统一,假如double返回的是null
    五、 Retrofit实现cookie自动化管理
    六、 接口成功失败路由判断,处理格式异常情况,如code=1成功,data={},code=100,data=null
    七、 Retrofit配置及各情况处理(缓存拦截、日志打印、替换接口内容、参数添加等)
    八、 Retrofit文件上传(封装中有,暂未实践)
    九、 Retrofit文件下载(封装中有,暂未实践)
    十、 后记
    十一、 本文譩在一篇文章搞定所有,上述描述文章都有讲解

    一、MVP+Retrofit2+Okhttp3+Rxjava2+RxHarmony框架基本搭建

    1、我们需要依赖相关第三方库
    networkDeps = [
                "okhttp"             : 'com.squareup.okhttp3:okhttp:4.2.2',
                "retrofit"           : 'com.squareup.retrofit2:retrofit:2.6.2',
                "converter-gson"     : 'com.squareup.retrofit2:converter-gson:2.6.2',
                "adapter-rxjava2"    : 'com.squareup.retrofit2:adapter-rxjava2:2.6.2',
                "logging-interceptor": 'com.squareup.okhttp3:logging-interceptor:3.12.0'
        ]
    
        networkLibs = networkDeps.values()
    
    2、创建接口类ApiServer,定义接口方法
    public interface ApiServer {
        @FormUrlEncoded
        @POST("/api/table_list/")
        Observable<BaseModel<Object>> getCeShi(@FieldMap HashMap<String, String> params);
    }
    
    3、 上述1,2整理完毕,开始创建okhttp和Retrofit
    public class ApiRetrofit {
        private static ApiRetrofit mApiRetrofit;
        private Retrofit retrofit;
        private ApiServer apiServer;
        private static final int DEFAULT_TIMEOUT = 15;
        public static String mBaseUrl = BaseContent.baseUrl;
    
        public ApiRetrofit() {
            OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder();
            httpClientBuilder
                    .cookieJar(new CookieManger(App.getContext()))
                    .connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
                    .writeTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
                    .readTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS);
    
            retrofit = new Retrofit.Builder()
                    .baseUrl(mBaseUrl)
                    .addConverterFactory(GsonConverterFactory.create())
                    //支持RxJava2
                    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                    .client(httpClientBuilder.build())
                    .build();
    
            apiServer = retrofit.create(ApiServer.class);
        }
    
        public static ApiRetrofit getInstance() {
            if (mApiRetrofit == null) {
                synchronized (Object.class) {
                    if (mApiRetrofit == null) {
                        mApiRetrofit = new ApiRetrofit();
                    }
                }
            }
            return mApiRetrofit;
        }
    
        public ApiServer getApiService() {
            return apiServer;
        }
    }
    

    Retrofit和Okhttp搭配使用如上述内容所述,下边开始配合Rxjava使用

    4、 先封装个基本实体类BaseModle,下面会用到(准备工作)

    封装理由:一个项目一般情况下json返回格式外层都是统一的

    public class BaseModel<T> implements Serializable {
        private String msg;
        private int code;
        private T data;
        public BaseModel(int code, String msg) {
            this.code = code;
            this.msg = msg;
        }
        public String getMsg() {
            return msg;
        }
        public void setMsg(String msg) {
            this.msg = msg;
        }
        public int getCode() {
            return code;
        }
        public void setCode(int code) {
            this.code = code;
        }
        public T getData() {
            return data;
        }
        public void setData(T data) {
            this.data = data;
        }
    }
    
    5、 定义几个会用到的接口,来区分如网络开始,结束,进度条加载,错误码等(准备工作)
    public interface BaseView {
        /**--------------------------------------------*
         * 接口开始情况  这时我们可以显示 菊花圈 或显示下载进度条
         *-------------------------------------------*/
        void showLoading(Boolean isShowProgress);
    
        /**--------------------------------------------*
         * 接口请求完毕  这时我们可以  将菊花圈隐藏掉 或下载进度条隐藏掉
         *-------------------------------------------*/
        void hideLoading();
    
        /**--------------------------------------------*
         * 返回 非定义的code状态码,和msg  mType 区分异常时请求的接口是哪个
         *-------------------------------------------*/
        void onErrorState(BaseModel model, int mType);
    
        /**--------------------------------------------*
         * 如果是下载文件时,或上传文件, 此回调是  文件下载进度监听回调
         *-------------------------------------------*/
        void onProgress(int progress);
    }
    
    6、 BaseObserver封装,开始结合Rxjava使用,以下为订阅后回调代表含义,封装原因如下

    1、onStart为网络请求开始,我们可以将刚才创建的接口实现一下BaseViewshowLoading(),用来代表网络开始的菊花框显示
    2.onNext为网络返回的内容,这时我们就可以将显示的菊花框关闭掉,BaseViewhideLoading()
    3、onError为网络请求失败的返回状态,可以通过异常来区分网络失败原因,分析好的异常情况然后以接口形式回调出去,所以实现BaseView中的onErrorState方法,onErrorState(BaseModel model, int mType);有人会问type作用是什么,其实是用来区分请求的是哪个接口,因为所有失败异常我们统一回调一个方法,这样区分不出是哪个接口失败的,所以传入一个type值,然后再回传出去,可知哪个接口失败
    4、onComplete代表请求完毕,这里不做任何操作,关闭菊花圈已经在onNext中回掉了,当然,你也可以在这里回调,但是存在一定体验问题,可以自行测试下

    说明:如下封装包含其他逻辑判断,在下边文章专题中进行讲解,无关方法可以忽略
    public abstract class BaseObserver<T> extends DisposableObserver<BaseModel<T>> {
        protected BaseView mView;
        public static final int PARSE_ERROR = 10008;
        public static final int BAD_NETWORK = 10007;
        public static final int CONNECT_ERROR = 10006;
        public static final int CONNECT_TIMEOUT = 10005;
        public static final int CONNECT_N = 10004;
        //回传标识
        private int mType = 0;
        //true 展示进度条
        private Boolean isShowProgress = false;
        public BaseObserver(BaseView view) {
            this.mView = view;
        }
        public BaseObserver(BaseView view, int mType) {
            this.mView = view;
            this.mType = mType;
        }
        public BaseObserver(BaseView view, Boolean isShowProgress) {
            this.mView = view;
            this.isShowProgress = isShowProgress;
        }
        @Override
        protected void onStart() {
            if (mView != null) mView.showLoading(isShowProgress);
        }
        @Override
        public void onNext(BaseModel<T> o) {
            try {
                if (mView != null) mView.hideLoading();
                if (BaseContent.getIsTrueCode(o.getCode())) {
                    onSuccessResult(o);
                } else {
                    onErrorResult(o);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        @Override
        public void onError(Throwable e) {
            if (mView != null) mView.hideLoading();
            if (e instanceof HttpException) {
                onErrorResult(new BaseModel<>(BAD_NETWORK, "网络超时"));
            } else if (e instanceof ConnectException ||
                    e instanceof UnknownHostException) {
                onErrorResult(new BaseModel<>(CONNECT_ERROR, "连接错误"));
            } else if (e instanceof InterruptedIOException) {        //  连接超时
                onErrorResult(new BaseModel<>(CONNECT_TIMEOUT, "连接超时"));
            } else if (e instanceof JsonParseException
                    || e instanceof ParseException) {
                onErrorResult(new BaseModel<>(PARSE_ERROR, "数据解析失败"));
            } else if (e instanceof ApiException) {
                /***************************************************************
                 * 重点说一下此种情况:此类是接口返回内容不规范,开发中肯定会存在这样类似问题,虽不是前端问题,但前端也可以很好处理此类问题
                 * 假如正常情况 返回data为集合
                 * code:1
                 * msg:获取成功
                 * data[ 。。。]
                 *
                 * 当异常情况下,返回data:{}或者data:""
                 * code:0
                 * msg:获取失败
                 * data:{}或者data:""
                 *
                 * 这样我们定义好的类型Gson解析会失败,由于类型不统一,并报异常,发生此类情况,在不改动后台代码情况下,
                 * 一般通常我们会定义成object类型再手动解析,但这样很是麻烦,所以,可参考此种实现方式
                 *
                 * 实现原理:拦截gson解析,解析前一步,先解析一遍code,如果是定义正常的,继续向下解析,如果非正常情况,抛异常处理,
                 * 并且将接口返回的code,msg一并抛出,异常会在这里拦截!!!!
                 **************************************************************/
                ApiException apiException = (ApiException) e;
                onErrorResult(new BaseModel<>(apiException.getErrorCode(), apiException.getMessage()));
            } else {
                if (e != null) {
                    onErrorResult(new BaseModel<>(CONNECT_N, e.toString()));
                } else {
                    onErrorResult(new BaseModel<>(CONNECT_N, "未知错误"));
                }
            }
        }
        private void onSuccessResult(BaseModel<T> o) {
            onSuccess(o);
        }
        private void onErrorResult(BaseModel<T> o) {
            if (mView != null) mView.onErrorState(o, mType);
        }
        @Override
        public void onComplete() {
    
        }
        public abstract void onSuccess(BaseModel<T> o);
    }
    

    Rxjava逻辑如上,下边开始讲解如何将RetrofitokhttpRxjavaRxHarmony连贯起来使用

    7、 BasePresenter封装

    当我们使用Rxjavasubscribe订阅后,网络会立即触发,但是在请求中UIdestroy了怎么办,不及时取消订阅,可能会造成内存泄漏,这时候CompositeDisposable开始上场了,它可以对我们订阅的请求进行统一管理。
    大致三步走:
    1、在UI层创建的时候(比如onCreate之类的),实例化CompositeDisposable
    2、把subscribe订阅返回的Disposable对象加入管理器;
    3、UI销毁时清空订阅的对象。
    我们将其封装到P层

    public class BasePresenter<V extends BaseView> {
        private CompositeDisposable compositeDisposable;
        public V baseView;
        public BasePresenter(V baseView) {
            this.baseView = baseView;
        }
        /**
         * 解除绑定
         */
        public void detachView() {
            baseView = null;
            removeDisposable();
        }
        public V getBaseView() {
            return baseView;
        }
        public void addDisposable(Observable<?> observable, BaseObserver observer) {
            if (compositeDisposable == null) {
                compositeDisposable = new CompositeDisposable();
            }
            compositeDisposable.add(observable.subscribeOn(Schedulers.io())
                    .observeOn(HmOSSchedulers.mainThread())
                    .subscribeWith(observer));
        }
        public void addDisposable(Observable<?> observable, DisposableObserver observer) {
            if (compositeDisposable == null) {
                compositeDisposable = new CompositeDisposable();
            }
            compositeDisposable.add(observable.subscribeOn(Schedulers.io())
                    .observeOn(HmOSSchedulers.mainThread())
                    .subscribeWith(observer));
        }
        public void addFileDisposable(Observable<?> observable, FileObserver observer) {
            if (compositeDisposable == null) {
                compositeDisposable = new CompositeDisposable();
            }
            compositeDisposable.add(observable.subscribeOn(Schedulers.io())
                    .observeOn(HmOSSchedulers.mainThread())
                    .subscribeWith(observer));
        }
        public void removeDisposable() {
            if (compositeDisposable != null) {
                compositeDisposable.dispose();
            }
        }
    }
    
    8、 这时候Rxjava是在子线程中执行,需要将返回结果回调到主线程,rxandroid负责此任务,然而鸿蒙无法使用rxandroid,因为android通知类方法handler,鸿蒙的是EventHandler,所以方法不一样不可以使用,需要根据rxandroid原理,重写改装成RxHarmony,如下改装三个类,不由官方维护,目前正常使用

    HmOSSchedulers类

    public final class HmOSSchedulers {
        private static final class MainHolder {
            static final Scheduler DEFAULT = new HandlerScheduler(new EventHandler(EventRunner.getMainEventRunner() ));
        }
        private static final Scheduler MAIN_THREAD = RxHmOSPlugins.initMainThreadScheduler(
                new Callable<Scheduler>() {
                    @Override public Scheduler call() throws Exception {
                        return MainHolder.DEFAULT;
                    }
                });
        public static Scheduler mainThread() {
            return RxHmOSPlugins.onMainThreadScheduler(MAIN_THREAD);
        }
        public static Scheduler from(EventRunner eventRunner) {
            if (eventRunner == null) throw new NullPointerException("eventRunner == null");
            return new HandlerScheduler(new EventHandler(eventRunner));
        }
        private HmOSSchedulers() {
            throw new AssertionError("No instances.");
        }
    }
    

    HandlerScheduler类

    final class HandlerScheduler extends Scheduler {
       private final EventHandler handler;
       HandlerScheduler(EventHandler handler) {
           this.handler = handler;
       }
       @Override
       public Disposable scheduleDirect(Runnable run, long delay, TimeUnit unit) {
           if (run == null) throw new NullPointerException("run == null");
           if (unit == null) throw new NullPointerException("unit == null");
           run = RxJavaPlugins.onSchedule(run);
           ScheduledRunnable scheduled = new ScheduledRunnable(handler, run);
           handler.postTask(scheduled, unit.toMillis(delay));
           return scheduled;
       }
       @Override
       public Worker createWorker() {
           return new HandlerWorker(handler);
       }
       private static final class HandlerWorker extends Worker {
           private final EventHandler handler;
           private volatile boolean disposed;
           HandlerWorker(EventHandler handler) {
               this.handler = handler;
           }
           @Override
           public Disposable schedule(Runnable run, long delay, TimeUnit unit) {
               if (run == null) throw new NullPointerException("run == null");
               if (unit == null) throw new NullPointerException("unit == null");
               if (disposed) {
                   return Disposables.disposed();
               }
               run = RxJavaPlugins.onSchedule(run);
               ScheduledRunnable scheduled = new ScheduledRunnable(handler, run);
               handler.postTask(scheduled, unit.toMillis(delay));
               if (disposed) {
                   handler.removeAllEvent();
                   return Disposables.disposed();
               }
               return scheduled;
           }
           @Override
           public void dispose() {
               disposed = true;
               handler.removeAllEvent();
           }
           @Override
           public boolean isDisposed() {
               return disposed;
           }
       }
       private static final class ScheduledRunnable implements Runnable, Disposable {
           private final EventHandler handler;
           private final Runnable delegate;
           private volatile boolean disposed;
           ScheduledRunnable(EventHandler handler, Runnable delegate) {
               this.handler = handler;
               this.delegate = delegate;
           }
           @Override
           public void run() {
               try {
                   delegate.run();
               } catch (Throwable t) {
                   RxJavaPlugins.onError(t);
               }
           }
           @Override
           public void dispose() {
               disposed = true;
               handler.removeAllEvent();
           }
           @Override
           public boolean isDisposed() {
               return disposed;
           }
       }
    }
    

    RxHmOSPlugins类

    public final class RxHmOSPlugins {
        private static volatile Function<Callable<Scheduler>, Scheduler> onInitMainThreadHandler;
        private static volatile Function<Scheduler, Scheduler> onMainThreadHandler;
        public static void setInitMainThreadSchedulerHandler(Function<Callable<Scheduler>, Scheduler> handler) {
            onInitMainThreadHandler = handler;
        }
        public static Scheduler initMainThreadScheduler(Callable<Scheduler> scheduler) {
            if (scheduler == null) {
                throw new NullPointerException("scheduler == null");
            }
            Function<Callable<Scheduler>, Scheduler> f = onInitMainThreadHandler;
            if (f == null) {
                return callRequireNonNull(scheduler);
            }
            return applyRequireNonNull(f, scheduler);
        }
        public static void setMainThreadSchedulerHandler(Function<Scheduler, Scheduler> handler) {
            onMainThreadHandler = handler;
        }
        public static Scheduler onMainThreadScheduler(Scheduler scheduler) {
            if (scheduler == null) {
                throw new NullPointerException("scheduler == null");
            }
            Function<Scheduler, Scheduler> f = onMainThreadHandler;
            if (f == null) {
                return scheduler;
            }
            return apply(f, scheduler);
        }
        public static Function<Callable<Scheduler>, Scheduler> getInitMainThreadSchedulerHandler() {
            return onInitMainThreadHandler;
        }
        public static Function<Scheduler, Scheduler> getOnMainThreadSchedulerHandler() {
            return onMainThreadHandler;
        }
        public static void reset() {
            setInitMainThreadSchedulerHandler(null);
            setMainThreadSchedulerHandler(null);
        }
        static Scheduler callRequireNonNull(Callable<Scheduler> s) {
            try {
                Scheduler scheduler = s.call();
                if (scheduler == null) {
                    throw new NullPointerException("Scheduler Callable returned null");
                }
                return scheduler;
            } catch (Throwable ex) {
                throw Exceptions.propagate(ex);
            }
        }
        static Scheduler applyRequireNonNull(Function<Callable<Scheduler>, Scheduler> f, Callable<Scheduler> s) {
            Scheduler scheduler = apply(f,s);
            if (scheduler == null) {
                throw new NullPointerException("Scheduler Callable returned null");
            }
            return scheduler;
        }
        static <T, R> R apply(Function<T, R> f, T t) {
            try {
                return f.apply(t);
            } catch (Throwable ex) {
                throw Exceptions.propagate(ex);
            }
        }
        private RxHmOSPlugins() {
            throw new AssertionError("No instances.");
        }
    }
    

    相关逻辑已封装完毕,下面看下如何使用

    9、 接口请求三步骤,第一步骤写个接口,用来回调数据,如定义MainView,并继承BaseView
    public interface MainView extends BaseView {
        void onTextSuccess(BaseModel<TextBean> o);
    }
    
    10、 接口请求三步骤,第二步骤p层,继承BasePresenter,串联okhttpRetrofitRxjava
    public class MainPresenter extends BasePresenter<MainView> {
        public MainPresenter(MainView baseView) {
            super(baseView);
        }
        public void getTextApi() {
            HashMap<String, String> params = new HashMap<>();
            params.put("type", "junshi");
            params.put("key", "2c1cb93f8c7430a754bc3ad62e0fac06");
            addDisposable(apiServer.getText(params), new BaseObserver(baseView) {
                @Override
                public void onSuccess(BaseModel o) {
                    baseView.onTextSuccess((BaseModel<TextBean>) o);
                }
                @Override
                public void onError(String msg) {
                    if (baseView != null) {
                        baseView.showError(msg);
                    }
                }
            });
        }
    }
    
    11、 在AbilitySlice中进行网络请求案例如下,当然,现在页面回调这么多东西,很不美观,就会想到j将无关方法放到基类,会引发Base->AbilitySliceFraction写法,请看第二部分内容,BaseAbilitySliceBaseFraction封装
    public class TextSlice extends BaseAbilitySlice<MainPresenter>{
        @Override
        public int getUIContent() {
            return ResourceTable.Layout_slice_collection;
        }
        @Override
        public void initComponent() {
            MainPresenter presenter = new MainPresenter(this);
            //网络请求
            presenter.getTextApi();
        }
        @Override
        public void showLoading(Boolean isShowProgress) {
        /**--------------------------------------------*
         * 接口开始情况  这时我们可以显示 菊花圈 或显示下载进度条
         *-------------------------------------------*/
        }
        @Override
        public void hideLoading() {
        /**--------------------------------------------*
         * 接口请求完毕  这时我们可以  将菊花圈隐藏掉 或下载进度条隐藏掉
         *-------------------------------------------*/
        }
        @Override
        public void onProgress(int progress) {
        /**--------------------------------------------*
         * 如果是下载文件时,或上传文件, 此回调是  文件下载进度监听回调
         *-------------------------------------------*/
        }
        @Override
        public void onErrorState(BaseModel model, int mType) {
        /**--------------------------------------------*
         * 返回 非定义的code状态码,和msg  mType 区分异常时请求的接口是哪个
         *-------------------------------------------*/
        }
    }
    
    
    



    二、BaseAbilitySlice,BaseFraction封装,搭配框架使用

    1、 BaseAbilitySlice封装
    public abstract class BaseAbilitySlice<P extends BasePresenter> extends AbilitySlice implements BaseView {
        protected P mPresenter;
        public abstract int getUIContent();
        public abstract void initComponent();
        protected abstract P createPresenter();
        public Context mContext;
        public Intent intent;
    
        @Override
        protected void onStart(Intent intent) {
            super.onStart(intent);
            this.intent = intent;
            mContext = this;
            mPresenter = createPresenter();
            beforsetUIContent();
            super.setUIContent(getUIContent());
            this.initComponent();
        }
        public String getString(int resId) {
            try {
                return getResourceManager().getElement(resId).getString();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return "";
        }
        public int getColor(int colorId) {
            try {
                return getResourceManager().getElement(colorId).getColor();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return 0;
        }
        public FractionManager getFractionManager() {
            Ability ability = getAbility();
            if (ability instanceof FractionAbility) {
                FractionAbility fractionAbility = (FractionAbility) ability;
                return fractionAbility.getFractionManager();
            }
            return null;
        }
        public P getPresenter() {
            return mPresenter;
        }
        public void beforsetUIContent() {
    
        }
        @Override
        public void showLoading(Boolean isShowProgress) {
    
        }
        @Override
        public void hideLoading() {
    
        }
        @Override
        public void onProgress(int progress) {
    
        }
        @Override
        public void onErrorState(BaseModel model, int mType) {
            if (!BaseContent.getIsTrueCode(model.getCode())) {
                Toast.show(mContext, model.getMsg());
            }
        }
    }
    
    2、 BaseFraction封装
    public abstract class BaseFraction<P extends BasePresenter> extends Fraction implements BaseView {
        protected P mPresenter;
        protected Component mComponentView;
        public abstract int getUIContent();
        protected abstract P createPresenter();
        public abstract void initComponent();
        public abstract void initData();
        public Context mContext;
        @Override
        protected Component onComponentAttached(LayoutScatter scatter, ComponentContainer container, Intent intent) {
            mComponentView = scatter.parse(getUIContent(), container, false);
            mContext = getFractionAbility();
            mPresenter = createPresenter();
            initComponent();
            initData();
            return mComponentView;
        }
        @Override
        protected void onStart(Intent intent) {
            super.onStart(intent);
        }
        @Override
        protected void onActive() {
            super.onActive();
        }
        @Override
        protected void onForeground(Intent intent) {
            super.onForeground(intent);
        }
        public String getString(int resId) {
            try {
                return getFractionAbility().getResourceManager().getElement(resId).getString();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return "";
        }
        public int getColor(int colorId) {
            try {
                return getFractionAbility().getResourceManager().getElement(colorId).getColor();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return 0;
        }
        @Override
        public void showLoading(Boolean isShowProgress) {
    
        }
        @Override
        public void hideLoading() {
    
        }
        @Override
        public void onErrorState(BaseModel model, int mType) {
            if (!BaseContent.getIsTrueCode(model.getCode())) {
                Toast.show(mContext, model.getMsg());
            }
        }
        @Override
        public void onProgress(int progress) {
    
        }
    }
    

    注:显示dialog方法可以直接放到base里显示,这样每个页面就不用每次重写了

    3、 演示页面请求
    public class TextSlice extends BaseAbilitySlice<MainPresenter> implements MainView {
        @Override
        protected MainPresenter createPresenter() {
            return new MainPresenter(this);
        }
         @Override
        public int getUIContent() {
            return ResourceTable.Layout_slice_collection;
        }
        @Override
        public void initComponent() {
          //网络请求
            mPresenter.getTextApi();
        }
        @Override
        public void onTextSuccess(BaseModel<TextBean> o) {
            //我是网络请求成功后的结果
        }
    }
    

    三、Retrofit运行时动态改变BaseUrl解决方案,及动态改变retrofit.create(cls)的接口cls,来实现组件化思想如android的Arouter,和鸿蒙服务的理念可分可合可流转多entry包思想

    下面分为俩个部分来讲解对应实现原理,3.1:动态修改BaseUrl 3.2:动态修改retrofit.create(cls)的接口cls
    3.1、Retrofit运行时动态改变BaseUrl解决方案
    3.1.1、出现此类问题场景

    在项目开发中涉及到多个BaseUrl,但在我们使用Retrofit开发时可能会遇到多BaseUrl不是很好处理情况,下面来讲解下我的处理方案,原理很简单

    3.1.2、第一种解决方案

    简单粗暴解决方案,利用Retrofit请求优先级,因为Retrofit支持全路径,比如

     @GET("http://www.baidu.com")
     Observable<Object> getApi(@Path("param") String param);
    

    再比如

    @GET
    Observable<Object> getApi(@Url String fileUrl, @Query("param")String param);
    
    3.1.3、第二种解决方案

    Retrofit默认只能设置一个BaseUrl,没有提供其Api去修改,所以我们只能通过其他方案去实现,网上也有很多介绍的,但尝试用了下感觉很不理想,于是自己稍加封装了下,思路其实简单。

    思路:一个Retrofit只能设置一个BaseUrl,我们可以创建多个Retrofit不就可以了吗?个接口创建一个,再通过用完再销毁思想,这样也可以,但是不是很理想,我们可以再转换思想,有几个BaseUrl创建几个,问这样不会造成内存开销?答案是不会的,项目中BaseUrl不会出现N多个,所以不必考虑这个问题

    代码实现:在代码设计时可以尽可能去优化,所以当我们用到此BaseUrl时,再去创建,用不到不创建,这样便会出现个问题,怎样知道我应该使用哪个RetrofitRetrofit怎么去保存等问题,本人思路是创建成功便添加到集合缓存下载,使用的时候去比对集合中BaseUrl和当前是否匹配,如果一致从集合中获取,如果不一致去创建新的,如果使用没有传入BaseUrl便用默认的,实现代码如下

    3.1.4、一般创建Retrofit方法
    public class ApiRetrofit {
        private static ApiRetrofit mApiRetrofit;
        private Retrofit retrofit;
        private ApiServer apiServer;
        public static String mBaseUrl = BaseContent.baseUrl;
        public ApiRetrofit() {
            OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder();
            httpClientBuilder
                    .connectTimeout(10, TimeUnit.SECONDS)
                    .writeTimeout(10, TimeUnit.SECONDS)
                    .readTimeout(10, TimeUnit.SECONDS)
                    .retryOnConnectionFailure(true);//错误重联
            retrofit = new Retrofit.Builder()
                    .baseUrl(mBaseUrl )
                    .addConverterFactory(GsonConverterFactory.create())
                    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                    .client(httpClientBuilder.build())
                    .build();
            apiServer = retrofit.create(ApiServer.class);
        }
        public static ApiRetrofit getInstance() {
            if (mApiRetrofit == null) {
                synchronized (Object.class) {
                    if (mApiRetrofit == null) {
                        mApiRetrofit = new ApiRetrofit();
                    }
                }
            }
            return mApiRetrofit;
        }
    }
    
    3.1.5、对创建Retrofit稍加封装

    新建保存对象的集合

    private static List<Retrofit> mRetrofitList = new ArrayList<>();
    private static List<ApiRetrofit> mApiRetrofitList = new ArrayList<>();
    

    修改创建时候的逻辑,如果请求接口时传入BaseUrl,检测BaseUrl是否为空,如果为空使用默认接口,如果不为空,再从缓存的Retrofit中查找是否已经才创建过了,如果创建了用缓存的,如果没有创建则创建

    注:这块可以用正则检查下传入的url是否为正规的域名,再做下判断

    //创建Retrofit代码中加入
     apiServer = retrofit.create(ApiServer.class);
     mRetrofitList.add(retrofit);
    public static ApiRetrofit getInstance() {
            mBaseUrl = BaseContent.baseUrl;
            int mIndex = -1;
            for (int i = 0; i < mRetrofitList.size(); i++) {
                if (BaseContent.baseUrl.equals(mRetrofitList.get(i).baseUrl().toString())) {
                    mIndex = i;
                    break;
                }
            }
            //新的baseUrl
            if (mIndex == -1) {
                synchronized (Object.class) {
                    mApiRetrofit = new ApiRetrofit();
                    mApiRetrofitList.add(mApiRetrofit);
                    return mApiRetrofit;
                }
            } else {
                //以前已经创建过的baseUrl
                return mApiRetrofitList.get(mIndex);
            }
        }
        public static ApiRetrofit getInstance(String baseUrl) {
            if (!TextUtils.isEmpty(baseUrl)) {
                mBaseUrl = baseUrl;
            } else {
                mBaseUrl = BaseContent.baseUrl;
            }
            int mIndex = -1;
            for (int i = 0; i < mRetrofitList.size(); i++) {
                if (baseUrl.equals(mRetrofitList.get(i).baseUrl().toString())) {
                    mIndex = i;
                    break;
                }
            }
            //新的baseUrl
            if (mIndex == -1) {
                synchronized (Object.class) {
                    mApiRetrofit = new ApiRetrofit();
                    mApiRetrofitList.add(mApiRetrofit);
                    return mApiRetrofit;
                }
            } else {
                //以前已经创建过的baseUrl
                return mApiRetrofitList.get(mIndex);
            }
        }
    
    3.1.6、使用时写法

    地址可以写成常量

    ApiRetrofit.getInstance("http://www.baidu.com/").getApiService().getCeShi(params)
    
    3.2、态改变retrofit.create(cls)的接口cls,组件化思想很有必要
    3.2.1、当我们搭建组件化后,立马会想到每个组件用一个接口类,或者搭建组件化时,每个模块用一个接口类,这种需求肯定会存在,看如何来封装(其中包含文件下载拦截器拦截逻辑,和添加请求头等逻辑,可参考,可忽略)
    public class ApiRetrofit {
        private static Retrofit retrofit;
        private Gson gson;
        private static final int DEFAULT_TIMEOUT = 135;
        private static List<Retrofit> mRetrofitList = new ArrayList<>();
        public static String mBaseUrl = BaseContent.getBaseUrl();
        private static BaseView mBaseView = null;
        private static volatile Type mType = Type.BASE;
        public enum Type {
            FILE,
            BASE,
            BASE_URL,
        }
        public Type getType() {
            return mType;
        }
        public static void setType(Type type) {
            mType = type;
        }
        /**
         * 文件处理
         *
         * @param httpClientBuilder
         */
        public void initFileClient(OkHttpClient.Builder httpClientBuilder) {
            /**
             * 处理文件下载进度展示所需
             */
            httpClientBuilder.addNetworkInterceptor(new ProgressInterceptor());
        }
        /**
         * 默认所需
         *
         * @param httpClientBuilder
         */
        public void initDefaultClient(OkHttpClient.Builder httpClientBuilder) {
            /**
             * 处理一些识别识别不了 ipv6手机,如小米  实现方案  将ipv6与ipv4置换位置,首先用ipv4解析
             */
    //        httpClientBuilder.dns(new ApiDns());
            /**
             * 添加cookie管理
             * 方法1:第三方框架
             */
            PersistentCookieJar cookieJar = new PersistentCookieJar(new SetCookieCache(),
                    new SharedPrefsCookiePersistor(app));
            httpClientBuilder.cookieJar(cookieJar);
            /**
             * 添加cookie管理
             * 方法2:手动封装cookie管理
             */
    //        httpClientBuilder.cookieJar(new CookieManger(BaseApp.getContent()));
            /**
             * 添加日志拦截 实现方式1     上下俩种二者选其一即可
             */
    //        httpClientBuilder.addInterceptor(new JournalInterceptor());
            /**
             * 添加日志拦截 实现方式2     上下俩种二者选其一即可
             */
            HttpLoggingInterceptor logInterceptor = new HttpLoggingInterceptor(new HttpLogger());
            logInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
            httpClientBuilder.addInterceptor(logInterceptor);
            /**
             * 添加请求头
             */
    //        httpClientBuilder.addInterceptor(new HeadUrlInterceptor());
            /**
             * 忽略证书
             */
    //        httpClientBuilder.hostnameVerifier(new AllowAllHostnameVerifier());
        }
        public ApiRetrofit() {
            OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder();
            httpClientBuilder
                    .connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
                    .writeTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
                    .readTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
                    .retryOnConnectionFailure(true);//错误重联
            switch (getType()) {
                case FILE:
                    initFileClient(httpClientBuilder);
                    break;
                case BASE:
                case BASE_URL:
                    initDefaultClient(httpClientBuilder);
                    break;
            }
                retrofit = new Retrofit.Builder()
                        .baseUrl(mBaseUrl)
                        .addConverterFactory(GsonConverterFactory.create(buildGson()))
                        .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                        .client(httpClientBuilder.build())
                        .build();
            mRetrofitList.add(retrofit);
        }
        /**
         * 增加后台返回""和"null"的处理,如果后台返回格式正常
         * 1.int=>0
         * 2.double=>0.00
         * 3.long=>0L
         * 4.String=>""
         *
         * @return
         */
        public Gson buildGson() {
            if (gson == null) {
                gson = new GsonBuilder()
                        .registerTypeAdapter(Integer.class, new IntegerDefaultAdapter())
                        .registerTypeAdapter(int.class, new IntegerDefaultAdapter())
                        .registerTypeAdapter(Double.class, new DoubleDefaultAdapter())
                        .registerTypeAdapter(double.class, new DoubleDefaultAdapter())
                        .registerTypeAdapter(Long.class, new LongDefaultAdapter())
                        .registerTypeAdapter(long.class, new LongDefaultAdapter())
                        .registerTypeAdapter(String.class, new StringNullAdapter())
                        .create();
            }
            return gson;
        }
        private static <T> T create(Class<T> cls, String baseUrl) {
            mBaseUrl = baseUrl;
            if (retrofit == null) {
                new ApiRetrofit();
            } else {
                initRetrofit();
            }
            T t = retrofit.create(cls);
            return t;
        }
        private static void initRetrofit() {
            int mIndex = -1;
            for (int i = 0; i < mRetrofitList.size(); i++) {
                if (mBaseUrl.equals(mRetrofitList.get(i).baseUrl().toString())) {
                    mIndex = i;
                    break;
                }
            }
            //新的baseUrl
            if (mIndex == -1) {
                synchronized (Object.class) {
                    new ApiRetrofit();
                }
            } else {
                //已经创建过的baseUrl
                retrofit = mRetrofitList.get(mIndex);
            }
        }
        /**
         * 默认使用方式
         *
         * @return
         */
        public static <T> T getInstance(Class<T> cls) {
            setType(Type.BASE);
            mBaseView = null;
            return create(cls, BaseContent.getBaseUrl());
        }
        /**
         * 文件下载使用方式
         *
         * @param baseView
         * @return
         */
        public static <T> T getFileInstance(Class<T> cls, BaseView baseView) {
            setType(Type.FILE);
            mBaseView = baseView;
            return create(cls, BaseContent.getBaseUrl() + "file/");
        }
        /**
         * 动态改变baseUrl使用方式
         *
         * @param baseUrl
         * @return
         */
        public static <T> T getBaseUrlInstance(Class<T> cls, String baseUrl) {
            setType(Type.BASE_URL);
            mBaseView = null;
            return create(cls, baseUrl);
        }
      }
    
    3.2.2、使用时写法
    ApiRetrofit.getBaseUrlInstance(LiveApiServer.class, "http://www.baidu.com/").getCeShi(params)
    
    ApiRetrofit.getInstance(LiveApiServer.class).getCeShi(params)
    

    四、Retrofit,Gson解析,请求返回的类型不统一,假如double返回的是null

    现实开发中,往往会遇到后台返回数据格式不规范情况,比如前端字段原本定义为int类型,而数据返回为空,如果用Gson解析会导致解析失败,比如字段定义为double类型,而返回的格式为字符串null,导致解析失败等等(只在后台返回数据格式不规范情况下出现,如果后台返回格式规范并不用考虑此问题)

    1、 实现目标

    1、格式化数据不规范【格式化int类型数据】
    2、格式化数据不规范【格式化Long类型数据】
    3、格式化数据不规范【格式化Double类型数据】
    4、格式化数据不规范【格式化String类型数据】
    5、格式化数据不规范【格式化Null类型数据】

    2、 添加格式化工具方法到Gson解析中
         if (gson == null) {
                gson = new GsonBuilder()
                        .registerTypeAdapter(Integer.class, new IntegerDefaultAdapter())
                        .registerTypeAdapter(int.class, new IntegerDefaultAdapter())
                        .registerTypeAdapter(Double.class, new DoubleDefaultAdapter())
                        .registerTypeAdapter(double.class, new DoubleDefaultAdapter())
                        .registerTypeAdapter(Long.class, new LongDefaultAdapter())
                        .registerTypeAdapter(long.class, new LongDefaultAdapter())
                        .registerTypeAdapter(String.class, new StringNullAdapter())
                        .create();
            }
            return gson;
        }
    
    
     public ApiRetrofit() {
            OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder();
            httpClientBuilder
                    .connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
                    .writeTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
                    .readTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
                    .retryOnConnectionFailure(true);//错误重联
    
            retrofit = new Retrofit.Builder()
                    .baseUrl(BASE_SERVER_URL)
                    .addConverterFactory(GsonConverterFactory.create(buildGson()))//添加json转换框架buildGson()根据需求添加
                    //支持RxJava2
                    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                    .client(httpClientBuilder.build())
                    .build();
            apiServer = retrofit.create(ApiServer.class);
        }
    
    
    3、 对double类型处理,返回“”,或“null”,动态更改为默认值0.00,新建DoubleDefaultAdapter类
    public class DoubleDefault0Adapter implements JsonSerializer<Double>, JsonDeserializer<Double> {
        @Override
        public Double deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
            try {
                if (json.getAsString().equals("") || json.getAsString().equals("null")) {//定义为double类型,如果后台返回""或者null,则返回0.00
                    return 0.00;
                }
            } catch (Exception ignore) {
            }
            try {
                return json.getAsDouble();
            } catch (NumberFormatException e) {
                throw new JsonSyntaxException(e);
            }
        }
    
        @Override
        public JsonElement serialize(Double src, Type typeOfSrc, JsonSerializationContext context) {
            return new JsonPrimitive(src);
        }
    }
    
    4、 对int类型处理,返回“”,或“null”,动态更改为默认值0,新建DoubleDefaultAdapter类
    public class IntegerDefaultAdapter implements JsonSerializer<Integer>, JsonDeserializer<Integer> {
        @Override
        public Integer deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
                throws JsonParseException {
            try {
                if (json.getAsString().equals("") || json.getAsString().equals("null")) {//定义为int类型,如果后台返回""或者null,则返回0
                    return 0;
                }
            } catch (Exception ignore) {
            }
            try {
                return json.getAsInt();
            } catch (NumberFormatException e) {
                throw new JsonSyntaxException(e);
            }
        }
    
        @Override
        public JsonElement serialize(Integer src, Type typeOfSrc, JsonSerializationContext context) {
            return new JsonPrimitive(src);
        }
    }
    
    5、 对Long类型处理,返回“”,或“null”,动态更改为默认值0,新建DoubleDefaultAdapter类
    public class LongDefault0Adapter implements JsonSerializer<Long>, JsonDeserializer<Long> {
        @Override
        public Long deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
                throws JsonParseException {
            try {
                if (json.getAsString().equals("") || json.getAsString().equals("null")) {//定义为long类型,如果后台返回""或者null,则返回0
                    return 0l;
                }
            } catch (Exception ignore) {
            }
            try {
                return json.getAsLong();
            } catch (NumberFormatException e) {
                throw new JsonSyntaxException(e);
            }
        }
    
        @Override
        public JsonElement serialize(Long src, Type typeOfSrc, JsonSerializationContext context) {
            return new JsonPrimitive(src);
        }
    }
    
    5、 重点说一下String类型

    根据上边其他类型处理代码可以看出,String也就是把上述类中代码改成String就可以了,答案是可以的,如下,处理的内容为如果服务器返回字符串类型null,我们将其格式化成“”,空类型,但是我们为什么不直接写,请往下看

    public class StringDefaultConverter implements JsonSerializer<String>, JsonDeserializer<String> {
        @Override
        public String deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
            try {
                if (json.getAsString().equals("null")) {
                    return "";
                }
            } catch (Exception ignore) {
            }
            try {
                return json.getAsJsonPrimitive().getAsString();
            } catch (NumberFormatException e) {
                throw new JsonSyntaxException(e);
            }
        }
    
        @Override
        public JsonElement serialize(String src, Type typeOfSrc, JsonSerializationContext context) {
            return new JsonPrimitive(src);
        }
    }
    

    但是有种比较常见的不规范数据返回,为null,不是字符串的"null",是这个null,如果返回null,会进入到上边这个类吗,经过测试,返回null的直接跳过,所以出现了个问题,null到底是什么类型?

    通过读源码可知,我们可以自定义TypeAdapter,将其放入facotries中,并且gson在解析json时使用对应的TypeAdapter来的,而我们手动添加的TypeAdapter会优先于预设的TypeAdapter被使用。

    于是乎找到了一种其他方法来解决这个问题

    新建个类来集成TypeAdapter,这样就便优先于预设的TypeAdapter

    public class StringNullAdapter extends TypeAdapter<String> {
        @Override
        public String read(JsonReader reader) throws IOException {
            if (reader.peek() == JsonToken.NULL) {
                reader.nextNull();
                return "";//原先是返回Null,这里改为返回空字符串
            }
    
            String jsonStr = reader.nextString();
            if(jsonStr.equals("null")) {
                return "";
            }else {
                return jsonStr;
            }
        }
    
        @Override
        public void write(JsonWriter writer, String value) throws IOException {
            if (value == null) {
                writer.nullValue();
                return;
            }
            writer.value(value);
        }
    }
    

    定义的类型为String,这样为null的情况会都归这个类来处理,但是String的所有情况也会走里边的方法,所以为了同样的类型不执行俩遍,Stringnull都在此类处理,只处理一遍就可以了, 处理所有情况为返回null,或字符串"null",格式化为"" 空

    五、Retrofit实现cookie自动化管理

    对应文章解析
    在现实开发中,我们可能会遇到这样的需求,需要保持长登陆状态,登陆失效为服务器判断,在我们不想往接口添加任何参数处理时,我们便想到cookie

    最终实现效果为:登录成功后将将服务器返回的cookie保存到本地(每次接口请求成功,更新本地保存Cookie值,目的让本地的cookie值一直为最新的),下次请求接口时将本地最新cookie带上,用来告诉哪个用户与服务器之间的交互

    1、 第一种实现方方法(第三方库实现Cookie自动化管理)

    (1)依赖第三方库

    implementation 'com.github.franmontiel:PersistentCookieJar:v1.0.1'
    

    (2)创建OkHttpClient时添加cookieJar

     PersistentCookieJar cookieJar = new PersistentCookieJar(new  SetCookieCache(), new SharedPrefsCookiePersistor(context));
    
      OkHttpClient okHttpClient = new OkHttpClient().newBuilder()
                    .connectTimeout(10, TimeUnit.SECONDS)
                    .readTimeout(20, TimeUnit.SECONDS)
                    .addInterceptor(new LoginInterceptor())
                    .cookieJar(cookieJar)// 设置封装好的cookieJar
                    .build();
    
    2、 第二种实现方方法(涉及到相关三个类)

    (1)创建CookieManger类

    public class CookieManger implements CookieJar {
        private static Context mContext;
        private static PersistentCookieStore cookieStore;
        public CookieManger(Context context) {
            mContext = context;
            if (cookieStore == null) {
                cookieStore = new PersistentCookieStore(mContext);
            }
        }
        @Override
        public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
            if (cookies != null && cookies.size() > 0) {
                for (Cookie item : cookies) {
                    cookieStore.add(url, item);
                    if (item.name() != null && !TextUtils.isEmpty(item.name()) &&
                            item.value() != null && !TextUtils.isEmpty(item.value())) {
                        /*保存cookie到sp地方  可能会用到 */
    //                    PrefUtils.setString(mContext, "cookie_name", item.name());
    //                    PrefUtils.setString(mContext, "cookie_value", item.value());
                    }
                }
            }
        }
        @Override
        public List<Cookie> loadForRequest(HttpUrl url) {
            List<Cookie> cookies = cookieStore.get(url);
            for (int i = 0; i < cookies.size(); i++) {
                Log.e("", "拿出来的cookies name()==" + cookies.get(i).name());
                Log.e("", "拿出来的cookies value()==" + cookies.get(i).value());
            }
            return cookies;
        }
    }
    

    (2)创建OkHttpCookies类

    public class OkHttpCookies  implements Serializable {
        private transient final Cookie cookies;
        private transient Cookie clientCookies;
    
        public OkHttpCookies(Cookie cookies) {
            this.cookies = cookies;
        }
        public Cookie getCookies() {
            Cookie bestCookies = cookies;
            if (clientCookies != null) {
                bestCookies = clientCookies;
            }
            return bestCookies;
        }
        private void writeObject(ObjectOutputStream out) throws IOException {
            out.writeObject(cookies.name());
            out.writeObject(cookies.value());
            out.writeLong(cookies.expiresAt());
            out.writeObject(cookies.domain());
            out.writeObject(cookies.path());
            out.writeBoolean(cookies.secure());
            out.writeBoolean(cookies.httpOnly());
            out.writeBoolean(cookies.hostOnly());
            out.writeBoolean(cookies.persistent());
        }
        private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
            String name = (String) in.readObject();
            String value = (String) in.readObject();
            long expiresAt = in.readLong();
            String domain = (String) in.readObject();
            String path = (String) in.readObject();
            boolean secure = in.readBoolean();
            boolean httpOnly = in.readBoolean();
            boolean hostOnly = in.readBoolean();
            boolean persistent = in.readBoolean();
            Cookie.Builder builder = new Cookie.Builder();
            builder = builder.name(name);
            builder = builder.value(value);
            builder = builder.expiresAt(expiresAt);
            builder = hostOnly ? builder.hostOnlyDomain(domain) : builder.domain(domain);
            builder = builder.path(path);
            builder = secure ? builder.secure() : builder;
            builder = httpOnly ? builder.httpOnly() : builder;
            clientCookies =builder.build();
        }
    }
    

    (3)创建PersistentCookieStore类

    public class PersistentCookieStore {
        private static final String LOG_TAG = "PersistentCookieStore";
        private static final String COOKIE_PREFS = "Cookies_Prefs";
    
        private final Map<String, ConcurrentHashMap<String, Cookie>> cookies;
        private final SharedPreferences cookiePrefs;
        public PersistentCookieStore(Context context) {
            cookiePrefs = context.getSharedPreferences(COOKIE_PREFS, 0);
            cookies = new HashMap<>();
            //将持久化的cookies缓存到内存中 即map cookies
            Map<String, ?> prefsMap = cookiePrefs.getAll();
            for (Map.Entry<String, ?> entry : prefsMap.entrySet()) {
                String[] cookieNames = TextUtils.split((String) entry.getValue(), ",");
                for (String name : cookieNames) {
                    String encodedCookie = cookiePrefs.getString(name, null);
                    if (encodedCookie != null) {
                        Cookie decodedCookie = decodeCookie(encodedCookie);
                        if (decodedCookie != null) {
                            if (!cookies.containsKey(entry.getKey())) {
                                cookies.put(entry.getKey(), new ConcurrentHashMap<String, Cookie>());
                            }
                            cookies.get(entry.getKey()).put(name, decodedCookie);
                        }
                    }
                }
            }
        }
        protected String getCookieToken(Cookie cookie) {
            return cookie.name() + "@" + cookie.domain();
        }
        public void add(HttpUrl url, Cookie cookie) {
            String name = getCookieToken(cookie);
            //将cookies缓存到内存中 如果缓存过期 就重置此cookie
            if (!cookie.persistent()) {
                if (!cookies.containsKey(url.host())) {
                    cookies.put(url.host(), new ConcurrentHashMap<String, Cookie>());
                }
                cookies.get(url.host()).put(name, cookie);
            } else {
                if (cookies.containsKey(url.host())) {
                    cookies.get(url.host()).remove(name);
                }
            }
            //讲cookies持久化到本地
            SharedPreferences.Editor prefsWriter = cookiePrefs.edit();
            prefsWriter.putString(url.host(), TextUtils.join(",", cookies.get(url.host()).keySet()));
            prefsWriter.putString(name, encodeCookie(new OkHttpCookies(cookie)));
            prefsWriter.apply();
        }
        public List<Cookie> get(HttpUrl url) {
            ArrayList<Cookie> ret = new ArrayList<>();
            if (cookies.containsKey(url.host())) {
                ret.addAll(cookies.get(url.host()).values());
            }
            return ret;
        }
        public boolean removeAll() {
            SharedPreferences.Editor prefsWriter = cookiePrefs.edit();
            prefsWriter.clear();
            prefsWriter.apply();
            cookies.clear();
            return true;
        }
        public boolean remove(HttpUrl url, Cookie cookie) {
            String name = getCookieToken(cookie);
            if (cookies.containsKey(url.host()) && cookies.get(url.host()).containsKey(name)) {
                cookies.get(url.host()).remove(name);
                SharedPreferences.Editor prefsWriter = cookiePrefs.edit();
                if (cookiePrefs.contains(name)) {
                    prefsWriter.remove(name);
                }
                prefsWriter.putString(url.host(), TextUtils.join(",", cookies.get(url.host()).keySet()));
                prefsWriter.apply();
    
                return true;
            } else {
                return false;
            }
        }
        public List<Cookie> getCookies() {
            ArrayList<Cookie> ret = new ArrayList<>();
            for (String key : cookies.keySet()) {
                ret.addAll(cookies.get(key).values());
            }
            return ret;
        }
        /**
         * cookies 序列化成 string
         *
         * @param cookie 要序列化的cookie
         * @return 序列化之后的string
         */
        protected String encodeCookie(OkHttpCookies cookie) {
            if (cookie == null) {
                return null;
            }
            ByteArrayOutputStream os = new ByteArrayOutputStream();
            try {
                ObjectOutputStream outputStream = new ObjectOutputStream(os);
                outputStream.writeObject(cookie);
            } catch (IOException e) {
                Log.d(LOG_TAG, "IOException in encodeCookie", e);
                return null;
            }
            return byteArrayToHexString(os.toByteArray());
        }
        /**
         * 将字符串反序列化成cookies
         *
         * @param cookieString cookies string
         * @return cookie object
         */
        protected Cookie decodeCookie(String cookieString) {
            byte[] bytes = hexStringToByteArray(cookieString);
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
            Cookie cookie = null;
            try {
                ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
                cookie = ((OkHttpCookies) objectInputStream.readObject()).getCookies();
            } catch (IOException e) {
                Log.d(LOG_TAG, "IOException in decodeCookie", e);
            } catch (ClassNotFoundException e) {
                Log.d(LOG_TAG, "ClassNotFoundException in decodeCookie", e);
            }
            return cookie;
        }
        /**
         * 二进制数组转十六进制字符串
         *
         * @param bytes byte array to be converted
         * @return string containing hex values
         */
        protected String byteArrayToHexString(byte[] bytes) {
            StringBuilder sb = new StringBuilder(bytes.length * 2);
            for (byte element : bytes) {
                int v = element & 0xff;
                if (v < 16) {
                    sb.append('0');
                }
                sb.append(Integer.toHexString(v));
            }
            return sb.toString().toUpperCase(Locale.US);
        }
        /**
         * 十六进制字符串转二进制数组
         *
         * @param hexString string of hex-encoded values
         * @return decoded byte array
         */
        protected byte[] hexStringToByteArray(String hexString) {
            int len = hexString.length();
            byte[] data = new byte[len / 2];
            for (int i = 0; i < len; i += 2) {
                data[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4) + Character.digit(hexString.charAt(i + 1), 16));
            }
            return data;
        }
    }
    

    (4)创建OkHttpClient时添加cookieJar

    OkHttpClient okHttpClient = new OkHttpClient().newBuilder()
                    .connectTimeout(10, TimeUnit.SECONDS)
                    .readTimeout(20, TimeUnit.SECONDS)
                    .addInterceptor(new LoginInterceptor())
                    .cookieJar(new CookieManger (context))// 设置封装好的cookieJar
                    .build();
    

    六、接口成功失败路由判断,处理格式异常情况,如code=1成功,data={},code=100,data=null

             * 重点说一下此种情况:此类是接口返回内容不规范,开发中肯定会存在这样类似问题,虽不是前端问题,但前端也可以很好处理此类问题
             * 假如正常情况 返回data为集合
             * code:1
             * msg:获取成功
             * data[ 。。。]
             *
             * 当异常情况下,返回data:{}或者data:""
             * code:0
             * msg:获取失败
             * data:{}或者data:""
             *
             * 这样我们定义好的类型Gson解析会失败,由于类型不统一,并报异常,发生此类情况,在不改动后台代码情况下,
             * 一般通常我们会定义成object类型再手动解析,但这样很是麻烦,所以,可参考此种实现方式
             *
             * 实现原理:拦截gson解析,解析前一步,先解析一遍code,如果是定义正常的,继续向下解析,如果非正常情况,抛异常处理,
             * 并且将接口返回的code,msg一并抛出,异常会在这里拦截!!!!
    

    当我们处理后台返回数据时,我们会将成功需要的数据提取出来,失败的只提示一下msg,所以通过判断code来区分状态,一般情况下我们可以在onNext()中判断,如下

    @Override
        public void onNext(BaseModel<T> o) {
            T t = o.getData();
            try {
               /* if (t!=null){
                    L.e("返回数据="+o.toString());
                }else {
                    L.e("返回数据=null");
                }*/
                if (view != null) {
                    view.hideLoading();
                }
                if (o.getErrcode() == mSuccessCode) {
                    onSuccessResult(t, o.getMsg(), o.getErrcode());
                } else {
                    view.onErrorResult(o);
                }
    
            } catch (Exception e) {
                e.printStackTrace();
                onError(e.toString());
            }
        }
    

    假如code=1是成功,获取成功值从onSuccessResult中拿,失败值只要code,msg从回调中onErrorResult拿,
    返回的数据规范情况是没有问题的,但是,如果数据不规范,data原本需要{},但是返回了null,或者''",这样GOSN解析立马报异常,所以我们需要向,当我们执行到OnNext方法中,此时已经执行了Gson解析代码,所以我们是否可以将判断提前到Gson解析时候判断呢? 请看第二种方法

    2、 第二种判断方法,Gson解析期间判断

    如果想通过Gson解析期间判断,这样必然会设计到Gson源码如果走向,我们通过更改源码来自定义操作,通过阅读源码我们会发现解析数据会涉及到三个类,GsonConverterFactory,GsonRequestBodyConverter,GsonResponseBodyConverter这三个类,我们需要重写这个三个类,阅读代码会返现主要执行解析代码在GsonResponseBodyConverter中,所以我们的目标便是这里。

    思路:Gosn解析数据时,如果出现服务器下发非正常标识,此刻我们已判断服务器返回数据不是我们需要展示的,那我们解析到这一步已不用再向下解析,可以通过抛异常来释放当前任务代码如下

    @Override
        public T convert(ResponseBody value) throws IOException {
            String response = value.string();
            BaseResult re = gson.fromJson(response, BaseResult.class);
            //关注的重点,自定义响应码中非0的情况,一律抛出ApiException异常。
            //这样,我们就成功的将该异常交给onError()去处理了。
            if (re.getCode() != BaseContent.basecode) {
                value.close();
                throw new ApiException(re.getCode(), re.getMessage());
            }
    
            MediaType mediaType = value.contentType();
            Charset charset = mediaType != null ? mediaType.charset(UTF_8) : UTF_8;
            ByteArrayInputStream bis = new ByteArrayInputStream(response.getBytes());
            InputStreamReader reader = new InputStreamReader(bis, charset);
            JsonReader jsonReader = gson.newJsonReader(reader);
            try {
                return adapter.read(jsonReader);
            } finally {
                value.close();
            }
        }
    

    异常已成功抛出,那异常信息到哪里了呢?答案是到Rxjava的OnError中,异常我们抛的是自定义实体类ApiException,内含code,message,那我们到Rxjava中OnError获取到异常信息 e,e instanceof ApiException通过分析异常是否为我们自定义实体类来判断下一步如何操作,此方法为路由的第二种判断,示例如下

    @Override
        public void onError(Throwable e) {
            if (mView != null) mView.hideLoading();
    
            if (e instanceof HttpException) {
                onErrorResult(new BaseModel<>(BAD_NETWORK, "网络超时"));
            } else if (e instanceof ConnectException ||
                    e instanceof UnknownHostException) {
                onErrorResult(new BaseModel<>(CONNECT_ERROR, "连接错误"));
            } else if (e instanceof InterruptedIOException) {        //  连接超时
                onErrorResult(new BaseModel<>(CONNECT_TIMEOUT, "连接超时"));
            } else if (e instanceof JsonParseException
                    || e instanceof ParseException) {
                onErrorResult(new BaseModel<>(PARSE_ERROR, "数据解析失败"));
            } else if (e instanceof ApiException) {
                /***************************************************************
                 * 重点说一下此种情况:此类是接口返回内容不规范,开发中肯定会存在这样类似问题,虽不是前端问题,但前端也可以很好处理此类问题
                 * 假如正常情况 返回data为集合
                 * code:1
                 * msg:获取成功
                 * data[ 。。。]
                 *
                 * 当异常情况下,返回data:{}或者data:""
                 * code:0
                 * msg:获取失败
                 * data:{}或者data:""
                 *
                 * 这样我们定义好的类型Gson解析会失败,由于类型不统一,并报异常,发生此类情况,在不改动后台代码情况下,
                 * 一般通常我们会定义成object类型再手动解析,但这样很是麻烦,所以,可参考此种实现方式
                 *
                 * 实现原理:拦截gson解析,解析前一步,先解析一遍code,如果是定义正常的,继续向下解析,如果非正常情况,抛异常处理,
                 * 并且将接口返回的code,msg一并抛出,异常会在这里拦截!!!!
                 **************************************************************/
                ApiException apiException = (ApiException) e;
    
                onErrorResult(new BaseModel<>(apiException.getErrorCode(), apiException.getMessage()));
            } else {
                if (e != null) {
                    onErrorResult(new BaseModel<>(CONNECT_N, e.toString()));
                } else {
                    onErrorResult(new BaseModel<>(CONNECT_N, "未知错误"));
                }
            }
        }
        private void onSuccessResult(BaseModel<T> o) {
            onSuccess(o);
        }
        private void onErrorResult(BaseModel<T> o) {
            if (mView != null) mView.onErrorState(o, mType);
        }
    
    
        public abstract void onSuccess(BaseModel<T> o);
    

    七、Retrofit配置及各情况处理(缓存拦截、日志打印、替换接口内容、参数添加等

    相关参考跳转此链接

    八、后记

    如使用中遇到问题,后记中进行回答讲解

    相关文章

      网友评论

        本文标题:鸿蒙开发之网络框架搭建,MVP+Retrofit2+okhttp

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