RxPanda,歡迎使用和 star,提出的問題我會及時回覆並處理。java
基於 RxJava2
Retrofit2
Okhttp3
封裝的網絡庫,處理了數據格式封裝,gson 數據類型處理,gson 類解析空安全問題android
一、支持解析數據殼 key 自定義 二、支持接口單獨配置禁用脫殼返回接口定義的原始對象 三、支持多 host 校驗 四、支持日誌格式化及併發按序輸出 五、支持 data 爲基本數據類型 六、支持 int 類型 json 解析爲 String 不會 0 變成 0.0git
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 配置類進行更多的全局配置github
所有配置json
方法 | 說明 | 是否必須 |
---|---|---|
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 |
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 徹底同樣的基礎上增長了兩個自定義註解api
一、 @RealEntity安全
接口數據未使用 ApiData 進行數據殼包裝,須要直接解析未定義對象時使用。如上面代碼中的 ZhihuData
在解析時不會進行脫殼操做,接口返回 ZhihuData
就解析爲 ZhihuData
網絡
二、@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)
複製代碼
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
}
})
. . .
複製代碼
此方式直接使用,不須要第二步的接口定義
這只是一個最簡例子,能夠經過鏈式調用添加參數
請求頭
攔截器
標籤
等屬性
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:
複製代碼
LogEntity
日誌對象類,將一次請求的各個階段的日誌輸出暫存起來,到當次網絡請求結束時統一打印數據,打印時使用了線程安全的 LogPrinter 類有序輸出。(所以上線必定要關閉 Log(通常使用第一步的 BuildConfig.DEBUG 來動態配置),日誌的線程鎖會有性能損耗。)以 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);
}
};
複製代碼
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);
}
}
複製代碼
""
空字符串// 對於空類型不直接返回 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.**{*;}
複製代碼