美文网首页Android进阶之路Android技术知识Android开发
基于 RxJava2 、Retrofit2、Okhttp3 的封

基于 RxJava2 、Retrofit2、Okhttp3 的封

作者: PandaQ404 | 来源:发表于2019-08-13 10:19 被阅读16次

项目地址

RxPanda,欢迎使用和 star,提出的问题我会及时回复并处理。

RxPanda

基于 RxJava2 Retrofit2 Okhttp3 封装的网络库,处理了数据格式封装,gson 数据类型处理,gson 类解析空安全问题

1、支持解析数据壳 key 自定义
2、支持接口单独配置禁用脱壳返回接口定义的原始对象
3、支持多 host 校验
4、支持日志格式化及并发按序输出
5、支持 data 为基本数据类型
6、支持 int 类型 json 解析为 String 不会 0 变成 0.0

基本用法

一、全局配置推荐在 Application 初始化时配置

        RxPanda.globalConfig()
                .baseUrl(ApiService.BASE_URL) //配置基础域名
                .netInterceptor(new HttpLoggingInterceptor()
                        .setLevel(HttpLoggingInterceptor.Level.BODY)) //添加日志拦截器
                .apiSuccessCode(100L) // 数据壳解析时接口成功的状态码
                .hosts("http://192.168.0.107:8080") // 兼容另一个 host(默认只允许基础域名接口访问)
                .connectTimeout(10000) // 连接超时时间(ms)
                .readTimeout(10000) // 读取超时时间(ms)
                .writeTimeout(10000) // 写入超时时间(ms)
                .debug(BuildConfig.DEBUG);// 是否 dubug 模式(非 debug 模式不会输出日志)

以上只是精简的配置,还可以通过 GlobalConfig 配置类进行更多的全局配置

二、接口定义

public interface ApiService {

    // 在线 mock 正常使用 ApiData 数据壳
    @GET("xxx/xxx/xxx")
    Observable<List<ZooData>> getZooList();

    // 数据结构不变但是数据壳 jsonKey 与框架默认不一致时使用此注解,也可在 Config 配置全局使用此数据壳
    @ApiData(clazz = ZooApiData.class)
    @GET("xxx/xxx/xxx")
    Observable<List<ZooData>> newJsonKeyData();

    // 与 ApiData 结构完全不一样使用 RealEntity 标准不做脱壳处理,返回 ZhihuData 就解析为 ZhihuData
    @RealEntity
    @GET("xxx/xxx/xxx")
    Observable<ZhihuData> zhihu();
}

与 retrofit 完全一样的基础上增加了两个自定义注解

  • 1、 @RealEntity

    接口数据未使用 ApiData 进行数据壳包装,需要直接解析未定义对象时使用。如上面代码中的 ZhihuData 在解析时不会进行脱壳操作,接口返回 ZhihuData 就解析为 ZhihuData

  • 2、@ApiData(clazz = ZooApiData.class)

    接口数据使用 ApiData 进行数据壳包装,但包装的 key 与默认的 ApiData 不一致时,可自定义数数据壳实现 IApiData 接口

// 自定义解析 key
data class ZooApiData<T>(
    @SerializedName("errorCode") private val code: Long,
    @SerializedName("errorMsg") private val msg: String,
    @SerializedName("response") private val data: T
) : IApiData<T> {
    override fun getCode(): Long {
        return code
    }

    override fun getMsg(): String {
        return msg
    }

    override fun getData(): T {
        return data
    }

    override fun isSuccess(): Boolean {
        return code.toInt() == 100
    }

}

如果全部接口都是按 ZooApiData 的解析 key 格式返回的数据,也不用麻烦的每个接口都加注解。直接在第一步的配置中使用全局配置来配置全局的数据壳

  .apiDataClazz(ZooApiData::class.java)

三、请求使用

Retrofit 方式

    private val apiService = RxPanda.retrofit().create(ApiService::class.java)
    
                . . .
                
     apiService.zooList
                    .doOnSubscribe { t -> compositeDisposable.add(t) }
                    .compose(RxScheduler.sync())
                    .subscribe(object : ApiObserver<List<ZooData>>() {
                        override fun onSuccess(data: List<ZooData>?) {
                            // do something
                        }

                        override fun onError(e: ApiException?) {
                            // do something when error
                        }

                        override fun finished(success: Boolean) {
                            // do something when all finish
                        }
                    })
    
                . . .
    

Http 请求方式

此方式直接使用,不需要第二步的接口定义

  • GET 方式

这只是一个最简例子,可以通过链式调用添加参数 请求头 拦截器 标签 等属性

    RxPanda.get("https://www.xx.xx.xx/xx/xx/xx")
    .addParam(paramsMap)
    .tag("tags") // 可使用 RequestManager 根据 tag 管理请求
    .request(object :ApiObserver<List<ZooData>>(){
        override fun onSuccess(data: List<ZooData>?) {
            // do something
        }

        override fun onError(e: ApiException?) {
            // do something when error
        }

        override fun finished(success: Boolean) {
            // do something when all finish
        }

    })
  • POST 方式

这只是一个最简例子,可以通过链式调用添加参数 请求头 拦截器 标签 等属性

                RxPanda.post("xxxxxx")
                    .addHeader("header", "value")
                    .urlParams("key", "value")
                    .tag("ss")
                    .request(object : AppCallBack<String>() {
                        override fun success(data: String?) {

                        }

                        override fun fail(code: Long?, msg: String?) {

                        }

                        override fun finish(success: Boolean) {

                        }

                    })

文件上传

        RxPanda.upload("url")
            .addImageFile("key",file)
//            .addBytes("key",bytes)
//            .addStream("key",stream)
//            .addImageFile("key",file)
            .request(object : UploadCallBack() {
                override fun done(success: Boolean) {

                }

                override fun onFailed(e: Exception?) {

                }

                override fun inProgress(progress: Int) {

                }

            })

文件下载

        RxPanda.download("url")
            .target(file)
//            .target(path,fileName)
            .request(object : UploadCallBack() {
                override fun done(success: Boolean) {

                }

                override fun onFailed(e: Exception?) {

                }

                override fun inProgress(progress: Int) {

                }

            })

日志处理

  • 日志数据格式化
    以下是一次完整的网络请求,包含了数据和请求的基本参数数据
2019-08-13 10:04:02.088 22957-23059/com.pandaq.sample D/RxPanda:  
2019-08-13 10:04:02.088 22957-23059/com.pandaq.sample D/RxPanda: ╔════════════════════════  HTTP  START  ══════════════════════════
2019-08-13 10:04:02.088 22957-23059/com.pandaq.sample D/RxPanda: ║
2019-08-13 10:04:02.088 22957-23059/com.pandaq.sample D/RxPanda: ║==> GET https://www.easy-mock.com/mock/5cef4b3e651e4075bad237f8/example/customApiData http/1.1
2019-08-13 10:04:02.089 22957-23059/com.pandaq.sample D/RxPanda: ║Host: www.easy-mock.com
2019-08-13 10:04:02.089 22957-23059/com.pandaq.sample D/RxPanda: ║Connection: Keep-Alive
2019-08-13 10:04:02.089 22957-23059/com.pandaq.sample D/RxPanda: ║Accept-Encoding: gzip
2019-08-13 10:04:02.089 22957-23059/com.pandaq.sample D/RxPanda: ║User-Agent: okhttp/3.10.0
2019-08-13 10:04:02.089 22957-23059/com.pandaq.sample D/RxPanda: ║Info: GET
2019-08-13 10:04:02.089 22957-23059/com.pandaq.sample D/RxPanda: ║==> 200 OK https://www.easy-mock.com/mock/5cef4b3e651e4075bad237f8/example/customApiData (245ms)
2019-08-13 10:04:02.089 22957-23059/com.pandaq.sample D/RxPanda: ║Server: Tengine
2019-08-13 10:04:02.089 22957-23059/com.pandaq.sample D/RxPanda: ║Date: Tue, 13 Aug 2019 02:04:01 GMT
2019-08-13 10:04:02.089 22957-23059/com.pandaq.sample D/RxPanda: ║Content-Type: application/json; charset=utf-8
2019-08-13 10:04:02.089 22957-23059/com.pandaq.sample D/RxPanda: ║Content-Length: 495
2019-08-13 10:04:02.089 22957-23059/com.pandaq.sample D/RxPanda: ║Connection: keep-alive
2019-08-13 10:04:02.089 22957-23059/com.pandaq.sample D/RxPanda: ║X-Request-Id: 71a77b24-9822-47df-94b1-fd477cfcdaa9
2019-08-13 10:04:02.089 22957-23059/com.pandaq.sample D/RxPanda: ║Vary: Accept, Origin
2019-08-13 10:04:02.089 22957-23059/com.pandaq.sample D/RxPanda: ║Rate-Limit-Remaining: 1
2019-08-13 10:04:02.089 22957-23059/com.pandaq.sample D/RxPanda: ║Rate-Limit-Reset: 1565661842
2019-08-13 10:04:02.089 22957-23059/com.pandaq.sample D/RxPanda: ║Rate-Limit-Total: 2
2019-08-13 10:04:02.094 22957-23059/com.pandaq.sample D/RxPanda: ║
2019-08-13 10:04:02.095 22957-23059/com.pandaq.sample D/RxPanda: ║——————————————————JSON START——————————————————
2019-08-13 10:04:02.095 22957-23059/com.pandaq.sample D/RxPanda: ║ {
2019-08-13 10:04:02.095 22957-23059/com.pandaq.sample D/RxPanda: ║   "errorCode": 100,
2019-08-13 10:04:02.095 22957-23059/com.pandaq.sample D/RxPanda: ║   "errorMsg": "我是错误信息",
2019-08-13 10:04:02.095 22957-23059/com.pandaq.sample D/RxPanda: ║   "response": [
2019-08-13 10:04:02.095 22957-23059/com.pandaq.sample D/RxPanda: ║     {
2019-08-13 10:04:02.095 22957-23059/com.pandaq.sample D/RxPanda: ║       "zooId": 28,
2019-08-13 10:04:02.095 22957-23059/com.pandaq.sample D/RxPanda: ║       "name": "成都市动物园",
2019-08-13 10:04:02.095 22957-23059/com.pandaq.sample D/RxPanda: ║       "englishName": "chengdu zoo",
2019-08-13 10:04:02.095 22957-23059/com.pandaq.sample D/RxPanda: ║       "address": "中国·四川·成都·成华区昭觉寺南路234号",
2019-08-13 10:04:02.095 22957-23059/com.pandaq.sample D/RxPanda: ║       "tel": "028-83516953"
2019-08-13 10:04:02.095 22957-23059/com.pandaq.sample D/RxPanda: ║     },
2019-08-13 10:04:02.095 22957-23059/com.pandaq.sample D/RxPanda: ║     {
2019-08-13 10:04:02.095 22957-23059/com.pandaq.sample D/RxPanda: ║       "zooId": 28,
2019-08-13 10:04:02.095 22957-23059/com.pandaq.sample D/RxPanda: ║       "name": "北京市动物园",
2019-08-13 10:04:02.096 22957-23059/com.pandaq.sample D/RxPanda: ║       "englishName": "beijing zoo",
2019-08-13 10:04:02.096 22957-23059/com.pandaq.sample D/RxPanda: ║       "address": "中国·北京·北京·XX路XX号",
2019-08-13 10:04:02.096 22957-23059/com.pandaq.sample D/RxPanda: ║       "tel": "028-83316953"
2019-08-13 10:04:02.096 22957-23059/com.pandaq.sample D/RxPanda: ║     },
2019-08-13 10:04:02.096 22957-23059/com.pandaq.sample D/RxPanda: ║     {
2019-08-13 10:04:02.096 22957-23059/com.pandaq.sample D/RxPanda: ║       "zooId": 28,
2019-08-13 10:04:02.096 22957-23059/com.pandaq.sample D/RxPanda: ║       "name": "重庆市动物园",
2019-08-13 10:04:02.096 22957-23059/com.pandaq.sample D/RxPanda: ║       "englishName": "chongqing zoo",
2019-08-13 10:04:02.096 22957-23059/com.pandaq.sample D/RxPanda: ║       "address": "中国·重庆·重庆·XX路XX号",
2019-08-13 10:04:02.096 22957-23059/com.pandaq.sample D/RxPanda: ║       "tel": "028-83513353"
2019-08-13 10:04:02.096 22957-23059/com.pandaq.sample D/RxPanda: ║     }
2019-08-13 10:04:02.096 22957-23059/com.pandaq.sample D/RxPanda: ║   ]
2019-08-13 10:04:02.096 22957-23059/com.pandaq.sample D/RxPanda: ║ }
2019-08-13 10:04:02.096 22957-23059/com.pandaq.sample D/RxPanda: ║——————————————————JSON END———————————————————
2019-08-13 10:04:02.096 22957-23059/com.pandaq.sample D/RxPanda: ║
2019-08-13 10:04:02.096 22957-23059/com.pandaq.sample D/RxPanda: ║Info: 495-byte body
2019-08-13 10:04:02.096 22957-23059/com.pandaq.sample D/RxPanda: ║
2019-08-13 10:04:02.096 22957-23059/com.pandaq.sample D/RxPanda: ╚════════════════════════  HTTP  END  ═══════════════════════════
2019-08-13 10:04:02.096 22957-23059/com.pandaq.sample D/RxPanda:  
  • 多线程并发请求时日志输出交错错乱的问题
    为了避免请求日志穿插问题,定义了 LogEntity 日志对象类,将一次请求的各个阶段的日志输出暂存起来,到当次网络请求结束时统一打印数据,打印时使用了线程安全的 LogPrinter 类有序输出。(因此上线一定要关闭 Log(一般使用第一步的 BuildConfig.DEBUG 来动态配置),日志的线程锁会有性能损耗。)

Gson 解析处理

以 String 类型解析 TypeAdapter 为例,其他处理可在 DefaultTypeAdapters 查看

    public static final TypeAdapter<String> STRING = new TypeAdapter<String>() {
        @Override
        public String read(JsonReader in) throws IOException {
            JsonToken peek = in.peek();
            if (peek == JsonToken.NULL) {
                in.nextNull();
                return "";
            }
            if (peek == JsonToken.NUMBER) {
                double dbNum = in.nextDouble();
                if (dbNum > Long.MAX_VALUE) {
                    return String.valueOf(dbNum);
                }
                // 如果是整数
                if (dbNum == (long) dbNum) {
                    return String.valueOf((long) dbNum);
                } else {
                    return String.valueOf(dbNum);
                }
            }
            /* coerce booleans to strings for backwards compatibility */
            if (peek == JsonToken.BOOLEAN) {
                return Boolean.toString(in.nextBoolean());
            }
            return in.nextString();
        }

        @Override
        public void write(JsonWriter out, String value) throws IOException {
            out.value(value);
        }
    };
  • number 类型转解析为字符串 1"1.0" 的问题
    Gson 解析由于 Gson 库默认的 ObjectTypeAdapter 中 Number 类型数据直接都解析为了 double 数据类型,因此会出现。当接口返回数据为 int 型,解析类中又定义为 String 类型的时候出现 1"1.0"的问题。
// 对 number 具体的类型进行判断,而不是一概而论的返回 double 类型
            if (peek == JsonToken.NUMBER) {
                double dbNum = in.nextDouble();
                if (dbNum > Long.MAX_VALUE) {
                    return String.valueOf(dbNum);
                }
                // 如果是整数
                if (dbNum == (long) dbNum) {
                    return String.valueOf((long) dbNum);
                } else {
                    return String.valueOf(dbNum);
                }
            }
  • 避免空指针问题
    重写 String 类型的 TypeAdapter 在类型为 null 时返回 ""空字符串
// 对于空类型不直接返回 null 而是返回 "" 避免空指针
            if (peek == JsonToken.NULL) {
                in.nextNull();
                return "";
            }

混淆打包

混淆打包需添加如下的过滤规则

-keep @android.support.annotation.Keep class * {*;}

-keep class android.support.annotation.Keep

-keepclasseswithmembers class * {
    @android.support.annotation.Keep <methods>;
}

-keepclasseswithmembers class * {
    @android.support.annotation.Keep <fields>;
}

-keepclasseswithmembers class * {
    @android.support.annotation.Keep <init>(...);
}

########### OkHttp3 ###########
-dontwarn okhttp3.logging.**
-keep class okhttp3.internal.**{*;}
-dontwarn okio.**

########### RxJava RxAndroid ###########
-dontwarn sun.misc.**
-keepclassmembers class rx.internal.util.unsafe.*ArrayQueue*Field* {
    long producerIndex;
    long consumerIndex;
}
-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueProducerNodeRef {
    rx.internal.util.atomic.LinkedQueueNode producerNode;
}
-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueConsumerNodeRef {
    rx.internal.util.atomic.LinkedQueueNode consumerNode;
}

########### Gson ###########
-keep class com.google.gson.stream.** { *; }
-keepattributes EnclosingMethod
# Gson 自定义相关
-keep class com.pandaq.rxpanda.entity.**{*;}
-keep class com.pandaq.rxpanda.gsonadapter.**{*;}

相关文章

网友评论

    本文标题:基于 RxJava2 、Retrofit2、Okhttp3 的封

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