基於 RxJava2 、Retrofit2、Okhttp3 的封裝庫——RxPanda 項目地址 接入方式 RxPanda Release Log 基本用法 日誌處理 Gson 解析處理 混淆打包

項目地址

RxPanda,歡迎使用和 star,提出的問題我會及時回覆並處理。

接入方式

dependencies {
    "com.pandaq:rxpanda:1.0.2"
}

RxPanda

基於 RxJava2 Retrofit2 Okhttp3 封裝的網絡庫,處理了數據格式封裝,gson 數據類型處理,gson 類解析空安全問題,使用時推薦使用 Release Log 中的最新版本目前爲 1.0.2版本。

1、支持解析數據殼 key 自定義
2、支持接口單獨配置禁用脫殼返回接口定義的原始對象
3、支持多 host 校驗
4、支持日誌格式化及併發按序輸出
5、支持 data 爲基本數據類型
6、支持 int 類型 json 解析爲 String 不會 0 變成 0.0
7、支持解析類型爲 intStringfloatdoublelongBigDecimaEmptyData 時 json 字段缺失。解析爲對象時自動使用默認值
8、支持 json 解析時解析類型爲第七條中的類型但是返回爲 null 時替換爲配置的默認值
9、兼容 PHP 接口 floatintdoublelong 類型無值時後端未處理返回空字符串導致解析失敗
10、支持開發階段單接口返回模擬json數據(適用於脫離後端接口開發,提高開發效率)

Release Log

  • 1.0.2: a、修復 int、float、double 類型數據空字符串不能補全的問題;b、新增註解@MockJsondebug 模式下替換模擬數據功能
  • 1.0.0: a、修復全局設置請求超時時間無效,會被 CONFIG 的默認超時時間覆蓋問題;b、默認超時時間與 okhttp 保持一致設置爲 10s
  • 0.2.6: 升級 Retrofit 版本以達到支持 kotlin suspend 關鍵字,配合協程使用
  • 0.2.5: Json 解析爲對象時,基本數據類型 null 值或缺失的情況下增加默認值兼容
  • 0.2.4: ApiException msg 空兼容性優化
  • 0.2.3: 兼容 Number 類型 data,接口無數據時返回空字符串會解析報錯的問題
  • 0.2.2: 日誌攔截器重複添加 bug 修復
  • 0.2.1: 新增 http 錯誤類型分組功能、retrofit 進行 post、get 請求適配公共參數添加、日誌打印通過攔截器添加的參數信息缺失問題
  • 0.2.0: 使用 LogPrinter 同步輸出併發請求日誌,避免日誌錯亂
  • 0.1.9: 兼容 boolean 類型的 data
  • 0.1.8: 兼容 Android 9.0 移除反射方式替換 GsonAdapter,改用註冊方式
  • 0.1.7:文件上傳下載支持
  • 0.1.6:fix 數字解析爲 String 類型時變成 double 類型字符串(1 按 String 解析變爲 1.0 bug)

基本用法

一、全局配置推薦在 Application 初始化時配置


        val defValues = NullDataValue()
        defValues.defBoolean = false
        defValues.defDouble = -1.0
        defValues.defFloat = -0.0f
        defValues.defInt = -1
        defValues.defLong =0L
        defValues.defString = ""

        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)
                .client(new OkHttpClient.Builder()) // 僅用作補充 OkHttpClient 配置
                .defaultValue(defValues) // gson 返回字段爲 null 或 字段缺失時,解析實體對象的基本類型默認值配置
                .debug(BuildConfig.DEBUG);// 是否 dubug 模式(非 debug 模式不會輸出日誌)

以上只是精簡的配置,還可以通過 GlobalConfig 配置類進行更多的全局配置

全部配置

方法 說明 是否必須
baseUrl() 基礎域名配置 true
hosts(String... hosts) 添加信任域名未配置默認只允許 baseUrl 配置的地址 false
trustAllHost(boolean trustAll) 是否信任所有域名優先級大於 hosts,配置此爲 true 則信任所有 host 不管是否添加 false
hostVerifier(@NonNull HostnameVerifier verifier) 配置 Host 驗證規則對象,未配置默認爲 SafeHostnameVerifier (與 hosts()、trustAllHost() 方法衝突,添加此配置後另兩個配置失效,驗證規則以此配置爲準) false
addCallAdapterFactory(@NonNull CallAdapter.Factory factory) 添加 CallAdapterFactory 未添加默認值爲 RxJava2CallAdapterFactory false
converterFactory(@NonNull Converter.Factory factory) 配置 ConverterFactory 未添加默認值爲 PandaConvertFactory false
callFactory(@NonNull Call.Factory factory) 配置 CallFactory false
sslFactory(@NonNull SSLSocketFactory factory) 配置 SSLFactory 未添加則通過 SSLManager 配置一個初始參數全爲 null 的默認對象 false
connectionPool(@NonNull ConnectionPool pool) 配置連接池,未配置則使用 Okhttp 默認 false
addGlobalHeader(@NonNull String key, String header) 添加一個全局的請求頭 false
globalHeader(@NonNull Map<String, String> headers) 設置全局請求頭,會將已有數據清除再添加 false
addGlobalParam(@NonNull String key, String param) 添加一個全局的請求參數 false
globalParams(@NonNull Map<String, String> params) 設置全局請求參數,會將已有數據清除再添加 false
retryDelayMillis(long retryDelay) 重試間隔時間 false
retryCount(int retryCount) 重試次數 false
interceptor(@NonNull Interceptor interceptor) 添加全局攔截器 false
netInterceptor(@NonNull Interceptor interceptor) 添加全局網絡攔截器 false
readTimeout(long readTimeout) 全局讀取超時時間 false
writeTimeout(long writeTimeout) 全局寫超時時間 false
connectTimeout(long connectTimeout) 全局連接超時時間 false
apiDataClazz(Class<? extends IApiData> clazz) Json解析接口數據結構外殼對象 參考 ApiData,未配置默認按 ApiData 解析,如結構不變 key 不一致則可以通過自定義 false
apiSuccessCode(Long apiSuccessCode) Json解析接口數據結構外殼對象爲 ApiData 結構時,配置成功 Code,默認值爲 0L false
debug(boolean debug) 配置是否爲 debug 模式,非 debug 模式網絡庫將不會輸出 日誌 false
defaultValue(NullDataValue defaultValue) 配置對應數據類型返回結果爲 null 或對應數據接口未返回時的默認值 false
client(new OkHttpClient.Builder()) 補充配置 OkHttpClient,相同的配置會被 RxPanda 配置項覆蓋,例如超時時長等 false

二、接口定義

    //使用全局配置的數據殼,默認爲 ApiData
    @GET("xxx/xxx/xxx")
    Observable<List<ZooData>> getZooList();

與 retrofit 完全一樣的基礎上增加了兩個自定義註解

  • 1、 @RealEntity
    接口數據未使用 ApiData 進行數據殼包裝,需要直接解析未定義對象時使用。如上面代碼中的 ZhihuData 在解析時不會進行脫殼操作,接口返回 ZhihuData 就解析爲 ZhihuData
        // 與 ApiData 結構完全不一樣使用 RealEntity 標準不做脫殼處理,返回 ZhihuData 就解析爲 ZhihuData
        @RealEntity
        @GET("xxx/xxx/xxx")
        Observable<ZhihuData> zhihu();
    
  • 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
    }

}

給特定接口指定解析殼

    // 數據結構不變但是數據殼 jsonKey 與框架默認不一致時使用此註解,也可在 Config 配置全局使用此數據殼
    @ApiData(clazz = ZooApiData.class)
    @GET("xxx/xxx/xxx")
    Observable<List<ZooData>> newJsonKeyData();

如果全部接口都是按 ZooApiData 的解析 key 格式返回的數據,也不用麻煩的每個接口都加註解。直接在第一步的配置中使用全局配置來配置全局的數據殼

  .apiDataClazz(ZooApiData::class.java)
  • 3、 @MockJson(json = jsonString)
    後端給出數據結構但接口尚在開發時,可通過此註解配置模擬數據(僅在 RxPanda debug 模式下有效)。使用時在對應的接口上此註解指定返回的 json 字符串,任意請求一個可請求通的接口即可
    // 給這個接口指定模擬返回的 json 爲 Constants.MOCK_JSON(僅當 RxPanda.globalConfig().isDebug()=true 時有效),請求地址爲任意能正常請求的地址即可
    @MockJson(json = Constants.MOCK_JSON)
    @GET("https://www.baidu.com")
    Observable<List<ZooData>> newJsonKeyData();

三、自動補全默認值數據實體對象

本地需要解析的 UserInfo 對象如下

public class UserInfo {
    private String userName;
    private String nickName;
    private Integer age;
    private String notExist;
}
// 接口返回的data
{
"code": 0,
"msg": "獲取成功",
"data": {
            "userName": "張三",
            "nickName": "二狗子",
            "age": "27"
        }
}

當接口返回的 json 缺少 notExits 時,解析結果的 UserInfo 對象中 notExist 中的值將是null。如果配置了defaultValue,則在解析後notExist 的值將會解析爲 defaultValue 中的對應值。

三、請求使用

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.**{*;}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章