Android进阶之路Android技术知识Android开发

基于 RxJava2 、Retrofit2、Okhttp3 的封

2019-08-13  本文已影响16人  PandaQ404

项目地址

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 完全一样的基础上增加了两个自定义注解

// 自定义解析 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 请求方式

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

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

    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
        }

    })

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

                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:  

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 具体的类型进行判断,而不是一概而论的返回 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);
                }
            }
// 对于空类型不直接返回 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.**{*;}
上一篇 下一篇

猜你喜欢

热点阅读