Android Retrofit+Rxjava

1,676 阅读3分钟

一、基础封装

1、库的引入

//RxJava的依赖包
implementation 'io.reactivex.rxjava3:rxandroid:3.0.0'
//RxAndroid的依赖包
implementation 'io.reactivex.rxjava3:rxjava:3.0.0'
//retrofit
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.retrofit2:converter-scalars:2.9.0'
implementation  'com.squareup.retrofit2:adapter-rxjava3:2.9.0'
//okhttp
implementation 'com.squareup.okhttp3:okhttp:4.9.3'
implementation 'com.squareup.okhttp3:logging-interceptor:4.9.3'
//gson
implementation 'com.google.code.gson:gson:2.9.0'
//Logger
implementation 'com.orhanobut:logger:2.2.0'

2、基础封装

网上资源很多不再详细阐述,数据的调用现在一般在ViewModel模块中,然后封装在LiveData,本篇重点在Retrofit,不再展开。本篇接口取自wanandroid,感谢鸿洋大神!

管理类ApiManager.java


public class ApiManager {

    public final String BASE_URL= "https://wanandroid.com/";

    private static ApiManager apiManager;
    private Retrofit retrofit;
    private OkHttpClient client;
    private ApiServer apiServer;

    public static ApiManager getInstance() {
        if (apiManager == null) {
            synchronized (Object.class) {
                if (apiManager == null) {
                    apiManager = new ApiManager();
                }
            }
        }
        return apiManager;
    }

    public ApiManager() {
        client = new OkHttpClient.Builder()
                //添加log拦截器
                .addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)) //okhttp默认的
                .connectTimeout(10, TimeUnit.SECONDS)
                .readTimeout(10, TimeUnit.SECONDS)
                .build();

        retrofit = new Retrofit.Builder()
                .baseUrl(BASE_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .addConverterFactory(ScalarsConverterFactory.create())
                //支持RxJava
                .addCallAdapterFactory(RxJava3CallAdapterFactory.create())
                .client(client)
                .build();

        apiServer = retrofit.create(ApiServer.class);
    }

    public ApiServer getApiService() {
        return apiServer;
    }

}

返回数据的解析BaseResponse.java

public class BaseResponse<T> {
    private int errorCode;
    private String errorMsg;
    private T data;
}

返回数据的处理BaseObserver.java

public abstract class BaseObserver<T> extends DisposableObserver<BaseResponse<T>> {

    private boolean isShowDialog;   //是否需要显示Dialog
    /**
     * 解析数据失败
     */
    public static final int PARSE_ERROR = 1001;
    /**
     * 网络问题
     */
    public static final int BAD_NETWORK = 1002;
    /**
     * 连接错误
     */
    public static final int CONNECT_ERROR = 1003;
    /**
     * 连接超时
     */
    public static final int CONNECT_TIMEOUT = 1004;

    /**
     * @param isShowDialog 是否需要展示Dialog
     */
    public BaseObserver(boolean isShowDialog) {
        this.isShowDialog = isShowDialog;
    }

    @Override
    protected void onStart() {
        showLoading();
    }

    @Override
    public void onNext(BaseResponse<T> response) {
        //根据架构自定义如何返回
        if (response.getErrorCode() == 0) {
            onSuccess(response.getData());
        } else {
            int errorCode = response.getErrorCode();
            String msg = response.getErrorMsg();
            onFail(errorCode, msg, response);
        }
    }

    @Override
    public void onError(Throwable e) {
        dismissDialog();
        if (e instanceof HttpException) {
            //   HTTP错误
            onException(BAD_NETWORK);
        } else if (e instanceof ConnectException
                || e instanceof UnknownHostException) {
            //   连接错误
            onException(CONNECT_ERROR);
        } else if (e instanceof InterruptedIOException) {
            //  连接超时
            onException(CONNECT_TIMEOUT);
        } else if (e instanceof JsonParseException
                || e instanceof JSONException
                || e instanceof ParseException) {
            //  解析错误
            onException(PARSE_ERROR);
        } else {
            if (e != null) {
                onError(e.toString());
            } else {
                onError("未知错误");
            }
        }
    }

    private void onException(int unknownError) {
        switch (unknownError) {
            case CONNECT_ERROR:
                onError("连接错误");
                break;

            case CONNECT_TIMEOUT:
                onError("连接超时");
                break;

            case BAD_NETWORK:
                onError("网络问题");
                break;

            case PARSE_ERROR:
                onError("解析数据失败");
                break;

            default:
                break;
        }
    }

    @Override
    public void onComplete() {
        dismissDialog();
    }

    public abstract void onSuccess(T o);

    public abstract void onFail(int errorCode, String errorMsg, BaseResponse<T> response);

    public abstract void onError(String msg);

    /**
     * 显示Dialog.
     * 体现思路,实现略
     */
    public void showLoading() {
        //TODO 略
    }

    /**
     * 隐藏Dialog
     * 体现思路,实现略
     */
    public void dismissDialog() {
        //TODO 略
    }

}

接口类ApiServer.java

public interface ApiServer {

    /**
     * 获取文章列表
     * @return
     */
    @GET("article/list/1/json")
    Observable<BaseResponse<ArticleListResp>> getArticleList();


    /**
     * 获取热词
     * @return
     */
    @GET("hotkey/json")
    Observable<BaseResponse<Object>> getHotKey();

}

直接在Activity中调用,这里省略LiveData和ViewModel的部分,封装也很简单。

//获取文章列表
getArticleList()


private void getArticleList() {
    ApiManager.getInstance().getApiService()
            .getArticleList()
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(new BaseObserver<ArticleListResp>(true) {
                @Override
                public void onSuccess(ArticleListResp resp) {

                }

                @Override
                public void onFail(int errorCode, String errorMsg, BaseResponse<ArticleListResp> response) {

                }

                @Override
                public void onError(String msg) {

                }
            });
}

日志输出如下:

2022-03-13 14:46:40.210 25122-25154/com.abc.rxjava3 I/okhttp.OkHttpClient: --> GET https://wanandroid.com/article/list/1/json
2022-03-13 14:46:40.210 25122-25154/com.abc.rxjava3 I/okhttp.OkHttpClient: --> END GET
2022-03-13 14:46:40.554 25122-25154/com.abc.rxjava3 I/okhttp.OkHttpClient: <-- 200 OK https://wanandroid.com/article/list/1/json (343ms)
2022-03-13 14:46:40.554 25122-25154/com.abc.rxjava3 I/okhttp.OkHttpClient: Server: Apache-Coyote/1.1
2022-03-13 14:46:40.554 25122-25154/com.abc.rxjava3 I/okhttp.OkHttpClient: Cache-Control: private
2022-03-13 14:46:40.554 25122-25154/com.abc.rxjava3 I/okhttp.OkHttpClient: Expires: Thu, 01 Jan 1970 08:00:00 CST
2022-03-13 14:46:40.554 25122-25154/com.abc.rxjava3 I/okhttp.OkHttpClient: Set-Cookie: JSESSIONID=009E1C654D8D5CD7C889DAFA23A2FB36; Path=/; Secure; HttpOnly
2022-03-13 14:46:40.554 25122-25154/com.abc.rxjava3 I/okhttp.OkHttpClient: Content-Type: application/json;charset=UTF-8
2022-03-13 14:46:40.554 25122-25154/com.abc.rxjava3 I/okhttp.OkHttpClient: Transfer-Encoding: chunked
2022-03-13 14:46:40.554 25122-25154/com.abc.rxjava3 I/okhttp.OkHttpClient: Date: Sun, 13 Mar 2022 06:46:39 GMT
2022-03-13 14:46:40.590 25122-25154/com.abc.rxjava3 I/okhttp.OkHttpClient: {"data":{"curPage":2,"datas":[{"apkLink":"","audit":1,"author":"","canEdit":false,"chapterId":502,"chapterName":"自助","collect":false,"courseId":13,"desc":"","descMd":"","envelopePic":"","fresh":false,"host":"","id":21691,"link":"https://juejin.cn/post/7072613093502615588","niceDate":"2022-03-08 21:40","niceShareDate":"2022-03-08 21:40","origin":"","prefix":"","projectLink":"","publishTime":1646746858000,"realSuperChapterId":493,"selfVisible":0,"shareDate":1646746834000,"shareUser":"鸿洋","superChapterId":494,"superChapterName":"广场Tab","tags":[],"title":"Android设备里的那些事儿|硬核科普","type":0,"userId":2,"visible":1,"zan":0},{"apkLink":"","audit":1,"author":"","canEdit":false,"chapterId":502,"chapterName":"自助","collect":false,"courseId":13,"desc":"","descMd":"","envelopePic":"","fresh":false,"host":"","id":21690,"link":"https://mp.weixin.qq.com/s/e1p9QdnCv-fFXgzO4XI74g","niceDate":"2022-03-08 21:40","niceShareDate":"2022-03-08 20:28","origin":"","prefix":"","projectLink":"","publishTime":1646746857000,"realSuperChapterId":493,"selfVisible":0,"shareDate":1646742494000,"shareUser":"趣阅编程","superChapterId":494,"superChapterName":"广场Tab","tags":[],"title":"Android 性能优化 - 通过 ASM 实现大图监控","type":0,"userId":13273,"visible":1,"zan":0},{"apkLink":"","audit":1,"author":"","canEdit":false,"chapterId":502,"chapterName":"自助","collect":false,"courseId":13,"desc":"","descMd":"","envelopePic":"","fresh":false,"host":"","id":21692,"link":"https://juejin.cn/post/7072541180667363358","niceDate":"2022-03-08 21:40","niceShareDate":"2022-03-08 21:40","origin":"","prefix":"","projectLink":"","publishTime":1646746853000,"realSuperChapterId":493,"selfVisible":0,"shareDate":1646746843000,"shareUser":"鸿洋","superChapterId":494,"superChapterName":"广场Tab","tags":[],"title":"CreationExtras 来了,创建 ViewModel 的新方式","type":0,"userId":2,"visible":1,"zan":0},{"apkLink":"","audit":1,"author":"","canEdit":false,"chapterId":502,"chapterName":"自助","collect":false,"courseId":13,"desc":"","descMd":"","envelopePic":"","fresh":false,"host":"","id":21688,"link":"https://juejin.cn/post/7072263857015619621","niceDate":"2022-03-08 09:12","niceShareDate":"2022-03-08 09:12","origin":"","prefix":"","projectLink":"","publishTime":1646701954000,"realSuperChapterId":493,"selfVisible":0,"shareDate":1646701954000,"shareUser":"易保山","superChapterId":494,"superChapterName":"广场Tab","tags":[],"title":"从Android源码角度谈设计模式(三):行为型模式","type":0,"userId":47679,"visible":1,"zan":0},{"apkLink":"","audit":1,"author":"","canEdit":false,"chapterId":502,"chapterName":"自助","collect":false,"courseId":13,"desc":"","descMd":"","envelopePic":"","fresh":false,"host":"","id":21687,"link":"https://juejin.cn/post/7051139976095858725","niceDate":"2022-03-08 09:12","niceShareDate":"2022-03-08 09:12","origin":"","prefix":"","projectLink":"","publishTime":1646701924000,"realSuperChapterId":493,"selfVisible":0,"shareDate":1646701924000,"shareUser":"易保山","superChapterId":494,"superChapterName":"广场Tab","tags":[],"title":"从Android源码角度谈设计模式(二):结构型模式","type":0,"userId":47679,"visible":1,"zan":0},{"apkLink":"","audit":1,"author":"","canEdit":false,"chapterId":502,"chapterName":"自助","collect":false,"courseId":13,"desc":"","descMd":"","envelopePic":"","fresh":false,"host":"","id":21686,"link":"https://juejin.cn/post/7043951828144226341","niceDate":"2022-03-08 09:11","niceShareDate":"2022-03-08 09:11","origin":"","prefix":"","projectLink":"","publishTime":1646701884000,"realSuperChapterId":493,"selfVisible":0,"shareDate":1646701884000,"shareUser":"易保山","superChapterId":494,"superChapterName":"广场Tab","tags":[],"title":"从Android源码角度谈设计模式(一):创建型模式","type":0,"userId":47679,"visible":1,"zan":0},{"apkLink":"","audit":1,"author":"鸿洋","canEdit":false,"chapterId":408,"chapterName":"鸿洋","collect":false,"courseId":13,"desc":"",
2022-03-13 14:46:40.590 25122-25154/com.abc.rxjava3 I/okhttp.OkHttpClient: "","publishTime":1646668800000,"realSuperChapterId":407,"selfVisible":0,"shareDate":1646748567000,"shareUser":"","superChapterId":408,"superChapterName":"公众号","tags":[{"name":"公众号","url":"/wxarticle/list/408/1"}],"title":"写了个适配检查工具,一键发现适配问题!","type":0,"userId":-1,"visible":1,"zan":0},{"apkLink":"","audit":1,"author":"Android群英传","canEdit":false,"chapterId":413,"chapterName":"Android群英传","collect":false,"courseId":13,"desc":"","descMd":"","envelopePic":"","fresh":false,"host":"","id":21694,"link":"https://mp.weixin.qq.com/s/z1mV_NLtnKjFuvoIAA2SRA","niceDate":"2022-03-08 00:00","niceShareDate":"2022-03-08 22:09","origin":"","prefix":"","projectLink":"","publishTime":1646668800000,"realSuperChapterId":407,"selfVisible":0,"shareDate":1646748580000,"shareUser":"","superChapterId":408,"superChapterName":"公众号","tags":[{"name":"公众号","url":"/wxarticle/list/413/1"}],"title":"Flutter布局指南之谁动了我的Key","type":0,"userId":-1,"visible":1,"zan":0},{"apkLink":"","audit":1,"author":"郭霖","canEdit":false,"chapterId":409,"chapterName":"郭霖","collect":false,"courseId":13,"desc":"","descMd":"","envelopePic":"","fresh":false,"host":"","id":21695,"link":"https://mp.weixin.qq.com/s/qGUGuCR4sXDifVnegmKrew","niceDate":"2022-03-08 00:00","niceShareDate":"2022-03-08 22:09","origin":"","prefix":"","projectLink":"","publishTime":1646668800000,"realSuperChapterId":407,"selfVisible":0,"shareDate":1646748592000,"shareUser":"","superChapterId":408,"superChapterName":"公众号","tags":[{"name":"公众号","url":"/wxarticle/list/409/1"}],"title":"Compose 的 State 状态到底是个啥?","type":0,"userId":-1,"visible":1,"zan":0},{"apkLink":"","audit":1,"author":"Afauria","canEdit":false,"chapterId":539,"chapterName":"未分类","collect":false,"courseId":13,"desc":"基于GitHub开源项目https://github.com/fengwensheng/getx_wanandroid开发。主要用于学习GetX框架、FlutterUI等","descMd":"","envelopePic":"https://www.wanandroid.com/blogimgs/eebdcecf-15e4-4895-a75a-725121807f75.png","fresh":false,"host":"","id":21683,"link":"https://www.wanandroid.com/blog/show/3150","niceDate":"2022-03-07 21:35","niceShareDate":"2022-03-07 21:35","origin":"","prefix":"","projectLink":"https://github.com/Afauria/Flutter-WanAndroid","publishTime":1646660138000,"realSuperChapterId":293,"selfVisible":0,"shareDate":1646660138000,"shareUser":"","superChapterId":294,"superChapterName":"开源项目主Tab","tags":[{"name":"项目","url":"/project/list/1?cid=539"}],"title":"GetX-WanAndroid","type":0,"userId":-1,"visible":1,"zan":0},{"apkLink":"","audit":1,"author":"","canEdit":false,"chapterId":502,"chapterName":"自助","collect":false,"courseId":13,"desc":"","descMd":"","envelopePic":"","fresh":false,"host":"","id":21674,"link":"https://juejin.cn/post/7071964490459250724","niceDate":"2022-03-07 21:25","niceShareDate":"2022-03-06 23:18","origin":"","prefix":"","projectLink":"","publishTime":1646659522000,"realSuperChapterId":493,"selfVisible":0,"shareDate":1646579920000,"shareUser":"深红骑士","superChapterId":494,"superChapterName":"广场Tab","tags":[],"title":"引用还是传值&mdash;&mdash;被打脸后才发现多年的理解是错的","type":0,"userId":29303,"visible":1,"zan":0},{"apkLink":"","audit":1,"author":"","canEdit":false,"chapterId":502,"chapterName":"自助","collect":false,"courseId":13,"desc":"","descMd":"","envelopePic":"","fresh":false,"host":"","id":21675,"link":"https://juejin.cn/post/7069013946929250311","niceDate":"2022-03-07 21:25","niceShareDate":"2022-03-06 23:23","origin":"","prefix":"","projectLink":"","publishTime":1646659520000,"realSuperChapterId":493,"selfVisible":0,"shareDate":1646580231000,"shareUser":"goweii","superChapterId":494,"superChapterName":"广场Tab","tags":[],"title":"Binder机制和AIDL的理解","type":0,"userId":20382,"visible":1,"zan":0},{"apkLink":"","audit":1,"author":"","canEdit":false,"chapterId":502,"chapterName":"自助","collect":false,"courseId":13,"desc":"","descMd
2022-03-13 14:46:40.590 25122-25154/com.abc.rxjava3 I/okhttp.OkHttpClient: e":1646659511000,"realSuperChapterId":493,"selfVisible":0,"shareDate":1646627793000,"shareUser":"古诚欺","superChapterId":494,"superChapterName":"广场Tab","tags":[],"title":"Mvi架构模式开发","type":0,"userId":127711,"visible":1,"zan":0},{"apkLink":"","audit":1,"author":"","canEdit":false,"chapterId":502,"chapterName":"自助","collect":false,"courseId":13,"desc":"","descMd":"","envelopePic":"","fresh":false,"host":"","id":21680,"link":"https://juejin.cn/post/7072020104212381732","niceDate":"2022-03-07 19:51","niceShareDate":"2022-03-07 19:51","origin":"","prefix":"","projectLink":"","publishTime":1646653919000,"realSuperChapterId":493,"selfVisible":0,"shareDate":1646653919000,"shareUser":"彭旭锐","superChapterId":494,"superChapterName":"广场Tab","tags":[],"title":"Android UI 架构演进:从 MVC 到 MVP、MVVM、MVI","type":0,"userId":30587,"visible":1,"zan":0},{"apkLink":"","audit":1,"author":"","canEdit":false,"chapterId":502,"chapterName":"自助","collect":false,"courseId":13,"desc":"","descMd":"","envelopePic":"","fresh":false,"host":"","id":21679,"link":"https://blog.csdn.net/zxm528/article/details/122351407?spm=1001.2014.3001.5501","niceDate":"2022-03-07 18:04","niceShareDate":"2022-03-07 18:04","origin":"","prefix":"","projectLink":"","publishTime":1646647441000,"realSuperChapterId":493,"selfVisible":0,"shareDate":1646647441000,"shareUser":"zxm528","superChapterId":494,"superChapterName":"广场Tab","tags":[],"title":"Flutter从0到1开发一个app:知天气","type":0,"userId":3487,"visible":1,"zan":0},{"apkLink":"","audit":1,"author":"","canEdit":false,"chapterId":502,"chapterName":"自助","collect":false,"courseId":13,"desc":"","descMd":"","envelopePic":"","fresh":false,"host":"","id":21676,"link":"https://mp.weixin.qq.com/s?__biz=MzU5NjkxMjE5Mg==&amp;mid=2247484594&amp;idx=1&amp;sn=bd31cddb9e6854e1afe7a7e024d6303f&amp;chksm=fe5a359dc92dbc8b751f75be4fe90c891658b3528fc2ee531fefb2a74bf4c88625883279b3b9&amp;token=994800008&amp;lang=zh_CN#rd","niceDate":"2022-03-07 08:31","niceShareDate":"2022-03-07 08:31","origin":"","prefix":"","projectLink":"","publishTime":1646613101000,"realSuperChapterId":493,"selfVisible":0,"shareDate":1646613101000,"shareUser":"音视频开发之旅","superChapterId":494,"superChapterName":"广场Tab","tags":[],"title":"学习实践CMake","type":0,"userId":107131,"visible":1,"zan":0},{"apkLink":"","audit":1,"author":"鸿洋","canEdit":false,"chapterId":408,"chapterName":"鸿洋","collect":false,"courseId":13,"desc":"","descMd":"","envelopePic":"","fresh":false,"host":"","id":21682,"link":"https://mp.weixin.qq.com/s/GFDSPHUuUxUKuAogR0XZ4Q","niceDate":"2022-03-07 00:00","niceShareDate":"2022-03-07 21:31","origin":"","prefix":"","projectLink":"","publishTime":1646582400000,"realSuperChapterId":407,"selfVisible":0,"shareDate":1646659894000,"shareUser":"","superChapterId":408,"superChapterName":"公众号","tags":[{"name":"公众号","url":"/wxarticle/list/408/1"}],"title":"让自定义View更加优雅的一个技巧!","type":0,"userId":-1,"visible":1,"zan":0},{"apkLink":"","audit":1,"author":"fmtjava","canEdit":false,"chapterId":539,"chapterName":"未分类","collect":false,"courseId":13,"desc":"基于微信小程序实现的一款精美仿开眼视频小程序(提供Flutter、Kotlin、ReactNative版本 :grin: )","descMd":"","envelopePic":"https://www.wanandroid.com/blogimgs/ebdbc335-0ff9-4e9a-9822-edb8ecccefb0.png","fresh":false,"host":"","id":21672,"link":"https://www.wanandroid.com/blog/show/3148","niceDate":"2022-03-06 22:24","niceShareDate":"2022-03-06 22:24","origin":"","prefix":"","projectLink":"https://github.com/fmtjava/wx_eyepetizer","publishTime":1646576675000,"realSuperChapterId":293,"selfVisible":0,"shareDate":1646576675000,"shareUser":"","superChapterId":294,"superChapterName":"开源项目主Tab","tags":[{"name":"项目","url":"/project/list/1?cid=539"}],"title":"基于微信小程序实现的一款精美仿开眼视频小程序(提供Flutter、Kotlin、ReactNative版本 😁 )","type":0,"userId":-1,"visible":
2022-03-13 14:46:40.590 25122-25154/com.abc.rxjava3 I/okhttp.OkHttpClient: cMd":"","envelopePic":"https://www.wanandroid.com/resources/image/pc/default_project_img.jpg","fresh":false,"host":"","id":21673,"link":"https://www.wanandroid.com/blog/show/3149","niceDate":"2022-03-06 22:21","niceShareDate":"2022-03-06 22:21","origin":"","prefix":"","projectLink":"https://github.com/ZuYun/pageviewj","publishTime":1646576485000,"realSuperChapterId":293,"selfVisible":0,"shareDate":1646576485000,"shareUser":"","superChapterId":294,"superChapterName":"开源项目主Tab","tags":[{"name":"项目","url":"/project/list/1?cid=539"}],"title":"flutter中viewpager的各种页面切换效果","type":0,"userId":-1,"visible":1,"zan":0},{"apkLink":"","audit":1,"author":"iDeMonnnnnn","canEdit":false,"chapterId":539,"chapterName":"未分类","collect":false,"courseId":13,"desc":"Activity Results API自动化注册&amp;极简使用方案。\r\n只调用一个带回调的函数实现startActivityForResult()/onActivityResult()。","descMd":"","envelopePic":"https://www.wanandroid.com/resources/image/pc/default_project_img.jpg","fresh":false,"host":"","id":21671,"link":"https://www.wanandroid.com/blog/show/3147","niceDate":"2022-03-06 22:17","niceShareDate":"2022-03-06 22:17","origin":"","prefix":"","projectLink":"https://github.com/iDeMonnnnnn/DeMon-ARA","publishTime":1646576224000,"realSuperChapterId":293,"selfVisible":0,"shareDate":1646576224000,"shareUser":"","superChapterId":294,"superChapterName":"开源项目主Tab","tags":[{"name":"项目","url":"/project/list/1?cid=539"}],"title":"Activity Results API自动化注册&amp;极简使用方案","type":0,"userId":-1,"visible":1,"zan":0}],"offset":20,"over":false,"pageCount":599,"size":20,"total":11970},"errorCode":0,"errorMsg":""}
2022-03-13 14:46:40.590 25122-25154/com.abc.rxjava3 I/okhttp.OkHttpClient: <-- END HTTP (14586-byte body)

二、日志的处理

希望日志能定位问题点,所以建议引入更加强大的日志库Logger--Simple, pretty and powerful logger for android,现在有一万三千多颗星,还是比较值得信赖的。

1、封装日志工具类

日志库的基础封装

/**
 * 日志类
 */
public class LogUtil {

    private static final String LOG_TAG = "SHOW_LOG";
    private static final String DEBUG = "debug";

    /**
     * 初始化log工具,在app入口处调用
     */
    public static void init() {
        FormatStrategy formatStrategy = PrettyFormatStrategy.newBuilder()
                .showThreadInfo(true)  // (Optional) Whether to show thread info or not. Default true
                .methodCount(0)         // (Optional) How many method line to show. Default 2
                .methodOffset(5)        // (Optional) Hides internal method calls up to offset. Default 5
                .logStrategy(new LogcatLogStrategy()) // (Optional) Changes the log strategy to print out. Default LogCat
                .tag(LOG_TAG)   // (Optional) Global tag for every log. Default PRETTY_LOGGER
                .build();

        Logger.addLogAdapter(new AndroidLogAdapter(formatStrategy) {
            @Override
            public boolean isLoggable(int priority, @Nullable String tag) {
                //用系统的BuildConfig.DEBUG(默认true),不要用Logger库中的BuildConfig.DEBUG(默认false)
                //true:打印日志 false:不打印日志
                return BuildConfig.BUILD_TYPE.equals(DEBUG);
            }
        });
    }

    public static void d(String message) {
        Logger.d(message);
    }

    public static void i(String message) {
        Logger.i(message);
    }

    /**
     * LogHelper.getStackTraceAsString(e)打印完整的Exception信息,Logger就能定位Exception的位置
     * e.toString 能打印Exception信息,但不能定位Exception的位置
     * @param e
     */
    public static void w(Throwable e) {
         String info = e != null ? LogHelper.getStackTraceAsString(e) : "null";
        Logger.w(info);
    }

    /**
     * LogHelper.getStackTraceAsString(e)打印完整的Exception信息,Logger就能定位Exception的位置
     * e.toString 能打印Exception信息,但不能定位Exception的位置
     * @param e
     */
    public static void e(Throwable e) {
        String info = e != null ? LogHelper.getStackTraceAsString(e) : "null";
        Logger.e(info);
    }

    public static void json(String json) {
        Logger.json(json);
    }
}

LogHelper.java

public class LogHelper {
    /**
     * 转换异常栈
     * @param throwable
     * @return
     */
    public static String getStackTraceAsString(Throwable throwable) {
        StringWriter writer = new StringWriter();
        throwable.printStackTrace(new PrintWriter(writer));
        return writer.toString();
    }
}

打印一个NullPointerException,可以定位Exception的位置,e.toString()不行

image.png

2、打印一个格式化的Log

日志在Android Studio中打印比较凌乱,并且细心一点会发现当日志超长时,日志之间会有字符丢失。见上方的日志输入:

image.png

如果直接在LoggerHttp的log方法中调用LogUtil.d(message),打印出来的日志是分散的,因为log方法是将一个网络请求的请求\响应行、header逐条打印的。但想要的效果是将同一个网络请求和响应的所有信息合并成一条日志,这样才方便调试时查看。
所以需要在LoggerHttp的log方法中做一些逻辑处理:

public class HttpLogger implements HttpLoggingInterceptor.Logger {
    private StringBuffer mMessage = new StringBuffer();
    @Override
    public void log(@NotNull String message) {

            // 请求或者响应开始
            if (message.startsWith("--> POST")) {
                mMessage.setLength(0);
            }
            // 以{}或者[]形式的说明是响应结果的json数据,需要进行格式化
            if ((message.startsWith("{") && message.endsWith("}"))
                    || (message.startsWith("[") && message.endsWith("]"))) {
                message = JsonUtil.formatJson(JsonUtil.decodeUnicode(message));
            }
            mMessage.append(message.concat("\n"));
            // 响应结束,打印整条日志
            if (message.startsWith("<-- END HTTP")) {
                LogUtil.d(mMessage.toString());
                //清除
                mMessage.setLength(0);
            }
    }
}

这里之所以没有采用looger库的Looger.json(String json)方法去打印json数据,是因为这个方法调用也会打印成单独的一条日志,不能实现将请求的所有信息在一条日志中。
JsonUtil是单独封装的一个将json格式化的工具,通过formatJson(String json)将json串格式化出清晰的层次结构。

/**
 * 格式化json字符串
 *
 * @param jsonStr 需要格式化的json串
 * @return 格式化后的json串
 */
public static String formatJson(String jsonStr) {
    if (null == jsonStr || "".equals(jsonStr)) return "";
    StringBuilder sb = new StringBuilder();
    char last = '\0';
    char current = '\0';
    int indent = 0;
    for (int i = 0; i < jsonStr.length(); i++) {
        last = current;
        current = jsonStr.charAt(i);
        //遇到{ [换行,且下一行缩进
        switch (current) {
            case '{':
            case '[':
                sb.append(current);
                sb.append('\n');
                indent++;
                addIndentBlank(sb, indent);
                break;
            //遇到} ]换行,当前行缩进
            case '}':
            case ']':
                sb.append('\n');
                indent--;
                addIndentBlank(sb, indent);
                sb.append(current);
                break;
            //遇到,换行
            case ',':
                sb.append(current);
                if (last != '\\') {
                    sb.append('\n');
                    addIndentBlank(sb, indent);
                }
                break;
            default:
                sb.append(current);
        }
    }
    return sb.toString();
}

/**
 * 添加space
 *
 * @param sb
 * @param indent
 */
private static void addIndentBlank(StringBuilder sb, int indent) {
    for (int i = 0; i < indent; i++) {
        sb.append('\t');
    }
}

decodeUnicode(String json)是将json中的Unicode编码转化为汉字编码(unicode编码的json中的汉字打印出来有可能是\u开头的字符串,所以需要处理)。

  /**
     * http 请求数据返回 json 中中文字符为 unicode 编码转汉字转码
     *
     * @param theString
     * @return 转化后的结果.
     */
    public static String decodeUnicode(String theString) {
        char aChar;
        int len = theString.length();
        StringBuffer outBuffer = new StringBuffer(len);
        for (int x = 0; x < len; ) {
            aChar = theString.charAt(x++);
            if (aChar == '\\') {
                aChar = theString.charAt(x++);
                if (aChar == 'u') {
                    int value = 0;
                    for (int i = 0; i < 4; i++) {
                        aChar = theString.charAt(x++);
                        switch (aChar) {
                            case '0':
                            case '1':
                            case '2':
                            case '3':
                            case '4':
                            case '5':
                            case '6':
                            case '7':
                            case '8':
                            case '9':
                                value = (value << 4) + aChar - '0';
                                break;
                            case 'a':
                            case 'b':
                            case 'c':
                            case 'd':
                            case 'e':
                            case 'f':
                                value = (value << 4) + 10 + aChar - 'a';
                                break;
                            case 'A':
                            case 'B':
                            case 'C':
                            case 'D':
                            case 'E':
                            case 'F':
                                value = (value << 4) + 10 + aChar - 'A';
                                break;
                            default:
                                throw new IllegalArgumentException(
                                        "Malformed   \\uxxxx   encoding.");
                        }

                    }
                    outBuffer.append((char) value);
                } else {
                    if (aChar == 't')
                        aChar = '\t';
                    else if (aChar == 'r')
                        aChar = '\r';
                    else if (aChar == 'n')
                        aChar = '\n';
                    else if (aChar == 'f')
                        aChar = '\f';
                    outBuffer.append(aChar);
                }
            } else
                outBuffer.append(aChar);
        }
        return outBuffer.toString();
    }

替换日志拦截器

//添加log拦截器
//.addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)) //okhttp默认的
.addInterceptor(new HttpLoggingInterceptor(new HttpLogger()).setLevel(HttpLoggingInterceptor.Level.BODY)) 

打印效果

2022-03-13 19:10:35.997 30574-30605/com.abc.rxjava3 D/SHOW_LOG: ┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────
2022-03-13 19:10:35.998 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ Thread: OkHttp https://wanandroid.com/...
2022-03-13 19:10:35.998 30574-30605/com.abc.rxjava3 D/SHOW_LOG: ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
2022-03-13 19:10:35.998 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ --> GET https://wanandroid.com/hotkey/json
2022-03-13 19:10:35.998 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ --> END GET
2022-03-13 19:10:35.998 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ <-- 200 OK https://wanandroid.com/hotkey/json (304ms)   <-------请求时间
2022-03-13 19:10:35.998 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ Server: Apache-Coyote/1.1
2022-03-13 19:10:35.998 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ Cache-Control: private
2022-03-13 19:10:35.998 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ Expires: Thu, 01 Jan 1970 08:00:00 CST
2022-03-13 19:10:35.998 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ Set-Cookie: JSESSIONID=D5E28B310AAF0666F28E6A44FA409894; Path=/; Secure; HttpOnly
2022-03-13 19:10:35.998 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ Content-Type: application/json;charset=UTF-8
2022-03-13 19:10:35.998 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ Transfer-Encoding: chunked
2022-03-13 19:10:35.998 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ Date: Sun, 13 Mar 2022 11:10:35 GMT
2022-03-13 19:10:35.998 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 
2022-03-13 19:10:35.998 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ {
2022-03-13 19:10:35.998 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 	"data":[
2022-03-13 19:10:35.998 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 		{
2022-03-13 19:10:35.998 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 			"id":6,
2022-03-13 19:10:35.998 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 			"link":"",
2022-03-13 19:10:35.998 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 			"name":"面试",
2022-03-13 19:10:35.998 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 			"order":1,
2022-03-13 19:10:35.998 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 			"visible":1
2022-03-13 19:10:35.998 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 		},
2022-03-13 19:10:35.998 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 		{
2022-03-13 19:10:35.998 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 			"id":9,
2022-03-13 19:10:35.998 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 			"link":"",
2022-03-13 19:10:35.998 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 			"name":"Studio3",
2022-03-13 19:10:35.998 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 			"order":1,
2022-03-13 19:10:35.998 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 			"visible":1
2022-03-13 19:10:35.998 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 		},
2022-03-13 19:10:35.998 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 		{
2022-03-13 19:10:35.998 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 			"id":5,
2022-03-13 19:10:35.998 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 			"link":"",
2022-03-13 19:10:35.998 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 			"name":"动画",
2022-03-13 19:10:35.998 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 			"order":2,
2022-03-13 19:10:35.998 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 			"visible":1
2022-03-13 19:10:35.998 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 		},
2022-03-13 19:10:35.999 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 		{
2022-03-13 19:10:35.999 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 			"id":1,
2022-03-13 19:10:35.999 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 			"link":"",
2022-03-13 19:10:35.999 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 			"name":"自定义View",
2022-03-13 19:10:35.999 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 			"order":3,
2022-03-13 19:10:35.999 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 			"visible":1
2022-03-13 19:10:35.999 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 		},
2022-03-13 19:10:35.999 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 		{
2022-03-13 19:10:35.999 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 			"id":2,
2022-03-13 19:10:35.999 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 			"link":"",
2022-03-13 19:10:35.999 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 			"name":"性能优化 速度",
2022-03-13 19:10:35.999 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 			"order":4,
2022-03-13 19:10:35.999 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 			"visible":1
2022-03-13 19:10:35.999 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 		},
2022-03-13 19:10:35.999 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 		{
2022-03-13 19:10:35.999 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 			"id":3,
2022-03-13 19:10:35.999 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 			"link":"",
2022-03-13 19:10:35.999 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 			"name":"gradle",
2022-03-13 19:10:35.999 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 			"order":5,
2022-03-13 19:10:35.999 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 			"visible":1
2022-03-13 19:10:35.999 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 		},
2022-03-13 19:10:35.999 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 		{
2022-03-13 19:10:35.999 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 			"id":4,
2022-03-13 19:10:35.999 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 			"link":"",
2022-03-13 19:10:35.999 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 			"name":"Camera 相机",
2022-03-13 19:10:35.999 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 			"order":6,
2022-03-13 19:10:35.999 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 			"visible":1
2022-03-13 19:10:35.999 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 		},
2022-03-13 19:10:35.999 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 		{
2022-03-13 19:10:35.999 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 			"id":7,
2022-03-13 19:10:35.999 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 			"link":"",
2022-03-13 19:10:35.999 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 			"name":"代码混淆 安全",
2022-03-13 19:10:35.999 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 			"order":7,
2022-03-13 19:10:35.999 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 			"visible":1
2022-03-13 19:10:35.999 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 		},
2022-03-13 19:10:35.999 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 		{
2022-03-13 19:10:35.999 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 			"id":8,
2022-03-13 19:10:35.999 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 			"link":"",
2022-03-13 19:10:35.999 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 			"name":"逆向 加固",
2022-03-13 19:10:35.999 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 			"order":8,
2022-03-13 19:10:35.999 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 			"visible":1
2022-03-13 19:10:35.999 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 		}
2022-03-13 19:10:35.999 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 	],
2022-03-13 19:10:35.999 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 	"errorCode":0,
2022-03-13 19:10:35.999 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ 	"errorMsg":""
2022-03-13 19:10:35.999 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ }
2022-03-13 19:10:35.999 30574-30605/com.abc.rxjava3 D/SHOW_LOG: │ <-- END HTTP (599-byte body)
2022-03-13 19:10:35.999 30574-30605/com.abc.rxjava3 D/SHOW_LOG: └────────────────────────────────────────────────────────────────────────────────────────────────────────────────

但是这样输出会有个问题,当多个接口同时请求时,有可能会打印重叠,但总体来说在使用过程中这样打印更方便,瑕不掩瑜吧。

三、多BaseUrl的处理

1、方式一:多Retrofit对象封装

public class ApiManager {

    public final String BASE_URL= "https://wanandroid.com/";
    public final String BASE_URL_2= "https://www.baidu.com/";

    private static ApiManager apiManager;
    private Retrofit retrofit;
    private Retrofit retrofit2;
    private OkHttpClient client;
    private ApiServer apiServer;
    private ApiServer apiServer2;

    public static ApiManager getInstance() {
        if (apiManager == null) {
            synchronized (Object.class) {
                if (apiManager == null) {
                    apiManager = new ApiManager();
                }
            }
        }
        return apiManager;
    }

    public ApiManager() {
        client = new OkHttpClient.Builder()
                //添加log拦截器
                //.addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)) //okhttp默认的
                .addInterceptor(new HttpLoggingInterceptor(new HttpLogger()).setLevel(HttpLoggingInterceptor.Level.BODY))
                .connectTimeout(10, TimeUnit.SECONDS)
                .readTimeout(10, TimeUnit.SECONDS)
                .build();

        retrofit = new Retrofit.Builder()
                .baseUrl(BASE_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .addConverterFactory(ScalarsConverterFactory.create())
                //支持RxJava
                .addCallAdapterFactory(RxJava3CallAdapterFactory.create())
                .client(client)
                .build();

        retrofit2 = new Retrofit.Builder()
                .baseUrl(BASE_URL_2)
                .addConverterFactory(GsonConverterFactory.create())
                .addConverterFactory(ScalarsConverterFactory.create())
                //支持RxJava
                .addCallAdapterFactory(RxJava3CallAdapterFactory.create())
                .client(client)
                .build();

        apiServer = retrofit.create(ApiServer.class);

        apiServer2 = retrofit2.create(ApiServer.class);
    }

    public ApiServer getApiService() {
        return apiServer;
    }

    public ApiServer getApiService2() {
        return apiServer2;
    }
}

使用

ApiManager.getInstance().getApiService()
或
ApiManager.getInstance().getApiService2()

2、方式二:在Interceptor中判断head

见文章【Android架构】基于MVP模式的Retrofit2+RXjava封装之多Url(七) 中通过添加Interceptor获取Headers,然后拆分,判断BaseUrl的方式。这种方式的拦截器要在日志拦截器之前。不推荐这种方式,虽然可以实现,但是打印日志会出问题。

3、方式三:使用反射

重新封装ApiManager


public class ApiManager {

    public static final String[] BASE_URL_SRR = {
            "https://www.baidu.com/",
            "https://wanandroid.com/",
            "https://www.juejin.com/"
    };

    private static ApiManager apiManager;
    private Retrofit retrofit;
    private Retrofit retrofitTwo;
    private OkHttpClient client;
    private ApiServer apiServer;

    public static ApiManager getInstance() {
        if (apiManager == null) {
            synchronized (Object.class) {
                if (apiManager == null) {
                    apiManager = new ApiManager();
                }
            }
        }
        return apiManager;
    }

    public ApiManager() {
        client = new OkHttpClient.Builder()
                //添加log拦截器
                .addInterceptor(new HttpLoggingInterceptor(new HttpLogger()).setLevel(HttpLoggingInterceptor.Level.BODY))
                .connectTimeout(10, TimeUnit.SECONDS)
                .readTimeout(10, TimeUnit.SECONDS)
                .build();

        Retrofit.Builder builder = new Retrofit.Builder()
                .baseUrl(BASE_URL_SRR[0])
                .addConverterFactory(GsonConverterFactory.create())
                .addConverterFactory(ScalarsConverterFactory.create())
                //支持RxJava
                .addCallAdapterFactory(RxJava3CallAdapterFactory.create())
                .client(client);
        //默认
        retrofit = builder.build();
        //用于反射修改BaseUrl
        retrofitTwo = builder.build();

        apiServer = retrofit.create(ApiServer.class);
    }

    public ApiServer getApiService() {
        return apiServer;
    }

    /**
     * 反射修改BaseUrl,封装方式根据需求来
     * @param isChangeBaseUrl true 需要修改  false 不需要
     * @param baseUrlIndex  baseUrl的数组索引
     * @return
     */
    public ApiServer getApiService(boolean isChangeBaseUrl, int baseUrlIndex) {
        if (isChangeBaseUrl) {
            if (baseUrlIndex < ApiManager3.BASE_URL_SRR.length) {
                try {
                    Field baseUrl = retrofitTwo.getClass().getDeclaredField("baseUrl");
                    baseUrl.setAccessible(true);
                    HttpUrl newBaseUrl = HttpUrl.parse(ApiManager.BASE_URL_SRR[baseUrlIndex]);
                    baseUrl.set(retrofitTwo, newBaseUrl);
                    return retrofitTwo.create(ApiServer.class);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        return apiServer;
    }

}

原理就是通过反射获取Retrofit对象的参数baseUrl并重新设置值。

调用

ApiManager.getInstance().getApiService(true,1)  //需要修改baseUrl,并且修改的baseUrl在数组中的索引为1
        .getHotKey()
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(new BaseObserver<Object>(false) {
            @Override
            public void onSuccess(Object resp) {

            }

            @Override
            public void onFail(int errorCode, String errorMsg, BaseResponse<Object> response) {

            }

            @Override
            public void onError(String msg) {

            }
        });

4、方式四:使用第三方库

这个库是比较老的库,但还可以用,RetrofitUrlManager。使用方法见文档。

四、刷新过期的Token

1、方式一:通过拦截器


public class TokenRefreshInterceptor implements Interceptor {
    @NotNull
    @Override
    public Response intercept(@NotNull Chain chain) throws IOException {
        Request request = chain.request();
        Response response = chain.proceed(request);

        if (response.body() != null) {
            BufferedSource buffer = Okio.buffer(response.body().source());
            String jsonString = buffer.readUtf8();

            try {
                JSONObject object = new JSONObject(jsonString);
                int errorCode = object.getInt("errorCode");
                if (errorCode == 202) {
                    //需要刷新token
                    OkHttpClient client = new OkHttpClient.Builder()
                            // .addInterceptor(new LogInterceptor())
                            //禁用代理
                            .proxy(Proxy.NO_PROXY)
                            .connectTimeout(10, TimeUnit.SECONDS)
                            .readTimeout(10, TimeUnit.SECONDS)
                            .build();

                    Retrofit retrofit = new Retrofit.Builder()
                            .baseUrl(ApiManager.BASE_URL)
                            //添加自定义的解析器
                            //支持RxJava2
                            .addConverterFactory(GsonConverterFactory.create())
                            .addCallAdapterFactory(RxJava3CallAdapterFactory.create())
                            .client(client)
                            .build();

                    ApiServer apiServer = retrofit.create(ApiServer.class);

                    //同步请求
                    Response tokenResponse = apiServer.refreshToken().execute();
                    if (tokenResponse.body() != null) {
                        //保存token
                        TokenCommon.saveToken(tokenResponse.body().get("token"));
                        TokenCommon.saveRefreshToken(tokenResponse.body().get("refreshToken"));

                        //添加token
                        Request newRequest = request.newBuilder()
                                .addHeader("Authorization", "Bearer " + TokenCommon.getToken())
                                .build();
                        response.close();
                        try {
                            return chain.proceed(newRequest);
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            } catch (JSONException e) {
                e.printStackTrace();
            }
        }
        return response;
    }
}

2、方式二:Rxjava的retryWhen操作符

以后在Rxjava的使用场景中详细写,此处略。

参考了以下文章,表示感谢:

利用logger打印完整的okhttp网络请求和响应日志

【Android架构】基于MVP模式的Retrofit2+RXjava封装之多Url(七)

【Android架构】基于MVP模式的Retrofit2+RXjava封装之Token的刷新(八)

--个人学习笔记