項目地址
RxPanda,歡迎使用和 star,提出的問題我會及時回復(fù)并處理。
接入方式
dependencies {
"com.pandaq:rxpanda:1.0.2"
}
RxPanda
基于 RxJava2 Retrofit2 Okhttp3 封裝的網(wǎng)絡(luò)庫,處理了數(shù)據(jù)格式封裝,gson 數(shù)據(jù)類型處理,gson 類解析空安全問題,使用時推薦使用 Release Log 中的最新版本目前為 1.0.2版本。
1、支持解析數(shù)據(jù)殼 key 自定義
2、支持接口單獨配置禁用脫殼返回接口定義的原始對象
3、支持多 host 校驗
4、支持日志格式化及并發(fā)按序輸出
5、支持 data 為基本數(shù)據(jù)類型
6、支持 int 類型 json 解析為 String 不會 0 變成 0.0
7、支持解析類型為int、String、float、double、long、BigDecima、EmptyData時 json 字段缺失。解析為對象時自動使用默認(rèn)值
8、支持 json 解析時解析類型為第七條中的類型但是返回為 null 時替換為配置的默認(rèn)值
9、兼容 PHP 接口float、int、double、long類型無值時后端未處理返回空字符串導(dǎo)致解析失敗
10、支持開發(fā)階段單接口返回模擬json數(shù)據(jù)(適用于脫離后端接口開發(fā),提高開發(fā)效率)
Release Log
- 1.0.2: a、修復(fù) int、float、double 類型數(shù)據(jù)空字符串不能補(bǔ)全的問題;b、新增注解
@MockJsondebug 模式下替換模擬數(shù)據(jù)功能- 1.0.0: a、修復(fù)全局設(shè)置請求超時時間無效,會被 CONFIG 的默認(rèn)超時時間覆蓋問題;b、默認(rèn)超時時間與 okhttp 保持一致設(shè)置為 10s
- 0.2.6: 升級 Retrofit 版本以達(dá)到支持 kotlin suspend 關(guān)鍵字,配合協(xié)程使用
- 0.2.5: Json 解析為對象時,基本數(shù)據(jù)類型 null 值或缺失的情況下增加默認(rèn)值兼容
- 0.2.4: ApiException msg 空兼容性優(yōu)化
- 0.2.3: 兼容 Number 類型 data,接口無數(shù)據(jù)時返回空字符串會解析報錯的問題
- 0.2.2: 日志攔截器重復(fù)添加 bug 修復(fù)
- 0.2.1: 新增 http 錯誤類型分組功能、retrofit 進(jìn)行 post、get 請求適配公共參數(shù)添加、日志打印通過攔截器添加的參數(shù)信息缺失問題
- 0.2.0: 使用 LogPrinter 同步輸出并發(fā)請求日志,避免日志錯亂
- 0.1.9: 兼容 boolean 類型的 data
- 0.1.8: 兼容 Android 9.0 移除反射方式替換 GsonAdapter,改用注冊方式
- 0.1.7:文件上傳下載支持
- 0.1.6:fix 數(shù)字解析為 String 類型時變成 double 類型字符串(1 按 String 解析變?yōu)?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) //配置基礎(chǔ)域名
.netInterceptor(new HttpLoggingInterceptor()
.setLevel(HttpLoggingInterceptor.Level.BODY)) //添加日志攔截器
.apiSuccessCode(100L) // 數(shù)據(jù)殼解析時接口成功的狀態(tài)碼
.hosts("http://192.168.0.107:8080") // 兼容另一個 host(默認(rèn)只允許基礎(chǔ)域名接口訪問)
.connectTimeout(10000) // 連接超時時間(ms)
.readTimeout(10000) // 讀取超時時間(ms)
.writeTimeout(10000) // 寫入超時時間(ms)
.client(new OkHttpClient.Builder()) // 僅用作補(bǔ)充 OkHttpClient 配置
.defaultValue(defValues) // gson 返回字段為 null 或 字段缺失時,解析實體對象的基本類型默認(rèn)值配置
.debug(BuildConfig.DEBUG);// 是否 dubug 模式(非 debug 模式不會輸出日志)
以上只是精簡的配置,還可以通過 GlobalConfig 配置類進(jìn)行更多的全局配置
全部配置
| 方法 | 說明 | 是否必須 |
|---|---|---|
| baseUrl() | 基礎(chǔ)域名配置 | true |
| hosts(String... hosts) | 添加信任域名未配置默認(rèn)只允許 baseUrl 配置的地址 | false |
| trustAllHost(boolean trustAll) | 是否信任所有域名優(yōu)先級大于 hosts,配置此為 true 則信任所有 host 不管是否添加 | false |
| hostVerifier(@NonNull HostnameVerifier verifier) | 配置 Host 驗證規(guī)則對象,未配置默認(rèn)為 SafeHostnameVerifier (與 hosts()、trustAllHost() 方法沖突,添加此配置后另兩個配置失效,驗證規(guī)則以此配置為準(zhǔn))
|
false |
| addCallAdapterFactory(@NonNull CallAdapter.Factory factory) | 添加 CallAdapterFactory 未添加默認(rèn)值為 RxJava2CallAdapterFactory
|
false |
| converterFactory(@NonNull Converter.Factory factory) | 配置 ConverterFactory 未添加默認(rèn)值為 PandaConvertFactory
|
false |
| callFactory(@NonNull Call.Factory factory) | 配置 CallFactory | false |
| sslFactory(@NonNull SSLSocketFactory factory) | 配置 SSLFactory 未添加則通過 SSLManager 配置一個初始參數(shù)全為 null 的默認(rèn)對象 | false |
| connectionPool(@NonNull ConnectionPool pool) | 配置連接池,未配置則使用 Okhttp 默認(rèn) | false |
| addGlobalHeader(@NonNull String key, String header) | 添加一個全局的請求頭 | false |
| globalHeader(@NonNull Map<String, String> headers) | 設(shè)置全局請求頭,會將已有數(shù)據(jù)清除再添加 | false |
| addGlobalParam(@NonNull String key, String param) | 添加一個全局的請求參數(shù) | false |
| globalParams(@NonNull Map<String, String> params) | 設(shè)置全局請求參數(shù),會將已有數(shù)據(jù)清除再添加 | false |
| retryDelayMillis(long retryDelay) | 重試間隔時間 | false |
| retryCount(int retryCount) | 重試次數(shù) | false |
| interceptor(@NonNull Interceptor interceptor) | 添加全局?jǐn)r截器 | false |
| netInterceptor(@NonNull Interceptor interceptor) | 添加全局網(wǎng)絡(luò)攔截器 | false |
| readTimeout(long readTimeout) | 全局讀取超時時間 | false |
| writeTimeout(long writeTimeout) | 全局寫超時時間 | false |
| connectTimeout(long connectTimeout) | 全局連接超時時間 | false |
| apiDataClazz(Class<? extends IApiData> clazz) | Json解析接口數(shù)據(jù)結(jié)構(gòu)外殼對象 參考 ApiData,未配置默認(rèn)按 ApiData 解析,如結(jié)構(gòu)不變 key 不一致則可以通過自定義 |
false |
| apiSuccessCode(Long apiSuccessCode) | Json解析接口數(shù)據(jù)結(jié)構(gòu)外殼對象為 ApiData 結(jié)構(gòu)時,配置成功 Code,默認(rèn)值為 0L
|
false |
| debug(boolean debug) | 配置是否為 debug 模式,非 debug 模式網(wǎng)絡(luò)庫將不會輸出 日志 | false |
| defaultValue(NullDataValue defaultValue) | 配置對應(yīng)數(shù)據(jù)類型返回結(jié)果為 null 或?qū)?yīng)數(shù)據(jù)接口未返回時的默認(rèn)值 | false |
| client(new OkHttpClient.Builder()) | 補(bǔ)充配置 OkHttpClient,相同的配置會被 RxPanda 配置項覆蓋,例如超時時長等 | false |
二、接口定義
//使用全局配置的數(shù)據(jù)殼,默認(rèn)為 ApiData
@GET("xxx/xxx/xxx")
Observable<List<ZooData>> getZooList();
與 retrofit 完全一樣的基礎(chǔ)上增加了兩個自定義注解
- 1、 @RealEntity
接口數(shù)據(jù)未使用 ApiData 進(jìn)行數(shù)據(jù)殼包裝,需要直接解析未定義對象時使用。如上面代碼中的ZhihuData在解析時不會進(jìn)行脫殼操作,接口返回ZhihuData就解析為ZhihuData// 與 ApiData 結(jié)構(gòu)完全不一樣使用 RealEntity 標(biāo)準(zhǔn)不做脫殼處理,返回 ZhihuData 就解析為 ZhihuData @RealEntity @GET("xxx/xxx/xxx") Observable<ZhihuData> zhihu(); - 2、@ApiData(clazz = ZooApiData.class)
接口數(shù)據(jù)使用 ApiData 進(jìn)行數(shù)據(jù)殼包裝,但包裝的 key 與默認(rèn)的 ApiData 不一致時,可自定義數(shù)數(shù)據(jù)殼實現(xiàn) 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
}
}
給特定接口指定解析殼
// 數(shù)據(jù)結(jié)構(gòu)不變但是數(shù)據(jù)殼 jsonKey 與框架默認(rèn)不一致時使用此注解,也可在 Config 配置全局使用此數(shù)據(jù)殼
@ApiData(clazz = ZooApiData.class)
@GET("xxx/xxx/xxx")
Observable<List<ZooData>> newJsonKeyData();
如果全部接口都是按 ZooApiData 的解析 key 格式返回的數(shù)據(jù),也不用麻煩的每個接口都加注解。直接在第一步的配置中使用全局配置來配置全局的數(shù)據(jù)殼
.apiDataClazz(ZooApiData::class.java)
- 3、 @MockJson(json = jsonString)
后端給出數(shù)據(jù)結(jié)構(gòu)但接口尚在開發(fā)時,可通過此注解配置模擬數(shù)據(jù)(僅在 RxPanda debug 模式下有效)。使用時在對應(yīng)的接口上此注解指定返回的 json 字符串,任意請求一個可請求通的接口即可
// 給這個接口指定模擬返回的 json 為 Constants.MOCK_JSON(僅當(dāng) RxPanda.globalConfig().isDebug()=true 時有效),請求地址為任意能正常請求的地址即可
@MockJson(json = Constants.MOCK_JSON)
@GET("https://www.baidu.com")
Observable<List<ZooData>> newJsonKeyData();
三、自動補(bǔ)全默認(rèn)值數(shù)據(jù)實體對象
本地需要解析的 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"
}
}
當(dāng)接口返回的 json 缺少 notExits 時,解析結(jié)果的 UserInfo 對象中 notExist 中的值將是null。如果配置了defaultValue,則在解析后notExist 的值將會解析為 defaultValue 中的對應(yīng)值。
三、請求使用
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 方式
這只是一個最簡例子,可以通過鏈?zhǔn)秸{(diào)用添加參數(shù) 請求頭 攔截器 標(biāo)簽 等屬性
RxPanda.get("https://www.xx.xx.xx/xx/xx/xx")
.addParam(paramsMap)
.tag("tags") // 可使用 RequestManager 根據(jù) 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 方式
這只是一個最簡例子,可以通過鏈?zhǔn)秸{(diào)用添加參數(shù) 請求頭 攔截器 標(biāo)簽 等屬性
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) {
}
})
日志處理
- 日志數(shù)據(jù)格式化
以下是一次完整的網(wǎng)絡(luò)請求,包含了數(shù)據(jù)和請求的基本參數(shù)數(shù)據(jù)
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": "中國·四川·成都·成華區(qū)昭覺寺南路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:
- 多線程并發(fā)請求時日志輸出交錯錯亂的問題
為了避免請求日志穿插問題,定義了LogEntity日志對象類,將一次請求的各個階段的日志輸出暫存起來,到當(dāng)次網(wǎng)絡(luò)請求結(jié)束時統(tǒng)一打印數(shù)據(jù),打印時使用了線程安全的 LogPrinter 類有序輸出。(因此上線一定要關(guān)閉 Log(一般使用第一步的 BuildConfig.DEBUG 來動態(tài)配置),日志的線程鎖會有性能損耗。)
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);
}
// 如果是整數(shù)
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 類型轉(zhuǎn)解析為字符串
1變"1.0"的問題
Gson 解析由于 Gson 庫默認(rèn)的 ObjectTypeAdapter 中 Number 類型數(shù)據(jù)直接都解析為了 double 數(shù)據(jù)類型,因此會出現(xiàn)。當(dāng)接口返回數(shù)據(jù)為 int 型,解析類中又定義為 String 類型的時候出現(xiàn)1變"1.0"的問題。
// 對 number 具體的類型進(jìn)行判斷,而不是一概而論的返回 double 類型
if (peek == JsonToken.NUMBER) {
double dbNum = in.nextDouble();
if (dbNum > Long.MAX_VALUE) {
return String.valueOf(dbNum);
}
// 如果是整數(shù)
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 "";
}
混淆打包
混淆打包需添加如下的過濾規(guī)則
-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 自定義相關(guān)
-keep class com.pandaq.rxpanda.entity.**{*;}
-keep class com.pandaq.rxpanda.gsonadapter.**{*;}