Shine——更简单的Android网络请求库封装
Posted 冬天的毛毛雨
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Shine——更简单的Android网络请求库封装相关的知识,希望对你有一定的参考价值。
作者:FreddyChen
写在前面
距离上一篇文章跟我一起开发商业级IM(3)—— 长连接稳定性之连接及重连发布的时间,大概已有一年多,先跟大家说声抱歉。主要是因为工作太忙,业务需求过多,没办法专心写博客。先立个Flag:IM系列文章一定会坚持写完,同时Github项目也会逐步完善,敬请期待。
这次就暂不更新IM系列相关的文章及项目了,先给大家带来一个稍微轻量级同时也比较实用的网络请求封装库:Shine,同时也希望自己借此机会重新拾起写博客和开源项目的激情,废话少说,我们直接开始吧。
Shine是什么?
基于Retrofit二次封装的网络请求库。通过统一封装、高内聚、低耦合、灵活配置、高度扩展等特性使Android网络请求更简单。
- 版本
- Java Retrofit+RxJava
- Kotlin Retrofit+Coroutine
Shine能做什么?
- 支持的请求
- GET
- POST
- PUT
- DELETE
- 支持动态BaseUrl
- 支持自定义Response Model(不同响应数据结构)
- 支持自定义Response Parser(响应数据解析器)
- 支持自定义Cipher(请求/响应数据加解密)
- 支持自定义Content Type
- 支持异步/同步请求
- 统一的IApiService,新增接口时无需改动IApiService
- 统一的异常处理,方便在接口请求失败时获取相关错误信息
为什么这样做?
- 不统一的Response Model
日常开发中,大家应该会经常遇到Response Model不统一的情况,例如服务端A返回的数据格式为:code、msg、data,服务端B返回的数据格式为:errCode、errMsg、result,服务端C返回的数据格式为status、message、data等,甚至即使是同一个服务端提供的接口,也可能存在不同接口返回不同数据格式,客户端兼容起来异常困难。在Shine中,通过自定义Response Model及Response Parser即可轻松解决此问题,同时支持配置全局Response Model及Response Parser,适应大多数单个服务器域名及返回数据格式的场景。
- 不同的BaseUrl
日常开发中,难免需要对接不同的服务器。Shine通过内部封装,使BaseUrl及Retrofit实例一一对应,应用层可配置全局BaseUrl或单个接口动态传递BaseUrl,使用灵活简单。
- 统一的IApiService
通常情况下,使用Retrofit请求接口的步骤为:
- 定义IApiService,声明接口;
- 在Model或Repository层调用接口;
- 在Presenter或ViewModel层调用Model实现的接口。
在Shine中,抽象为通用的IApiService,通过定义统一的get()
/post()
/put()
/delete()
/syncGet()
/syncPost()
/syncPut()
/syncDelete()
等接口,实现通用的IApiService,在新增接口或旧接口发生变动时,无需修改IApiService,降低开发成本并提升开发效率。
- 灵活的请求/响应Cipher(数据加解密器)
可配置全局Cipher或单个接口动态传递Cipher,灵活实现接口请求及响应数据加解密功能。例如接口A数据加密方式为AES,接口B数据加密方式为RSA等。
- 异步/同步请求支持
提供异步/同步请求方式支持。异步请求接口是我们平时请求的常用方式,但某些情况下,需要同步请求方式以实现某些需求,例如Ali OSS StsToken获取等。
- 统一的异常处理
通过封装RequestException实现统一异常处理,调用方仅需在自定义Response Model时构造对应的RequestException并传入错误码、错误信息等参数,使用Shine在接口请求失败时,通过RequestException提供的错误信息对业务做异常处理即可。
设计、封装思路及原理
-
项目结构 com.freddy.shine.kotlin
- cipher(数据加解密器相关)
- config(配置相关)
- exception(异常相关)
- interf(抽象接口相关)
- model(Response Model相关)
- parser(数据解析器相关)
- retrofit(Retrofit相关)
- utils(工具类相关)
- AbstractRequestManager.kt(RequestManager抽象类,自定义RequestManager需继承此类)
- RequestManagerFactory.kt(RequestManager工厂,提供获取RequestManager方法,应用层直接调用[getRequestManager]即可,无需关心内部实现逻辑)
- ShineKit.kt(Shine核心类)
-
设计及封装
Shine内部封装请求逻辑,同时提供以下方案使Shine更易用、更具扩展性:- 暴露ICipher接口使调用方灵活自定义相关数据加解密器实现,并可配置全局/单个接口请求使用;
- 暴露IParser接口使调用方灵活自定义相关数据解析器实现,并可配置全局/单个接口请求使用;
- 抽象统一的IApiService,支持异步/同步请求,并统一请求方式使Shine支持各项目使用;
- 内部多Retrofit实例管理使Shine支持动态BaseUrl;
- 通过构建者模式使Shine请求调用参数传递更灵活等。
-
原理
- Retrofit多实例管理:采用Map保存多个Retrofit实例,
key: BaseUrl, value: Retrofit Instance
。当然有些同学可能觉得多个Retrofit会造成性能浪费、不好管理之类的,这个就见仁见智了。我觉得在一个项目中BaseUrl并不会过多,并且如果是统一的OkHttpClient的话,多个Retrofit实例并不会造成多大的性能浪费,并且多个Retrofit反而会更灵活。当然,后续我会增加移除Retrofit实例的接口,大家如果觉得在某个时刻(大概率不再请求该BaseUrl)可以适当移除该Retrofit实例的话直接移除即可,即使会重新请求,那也就是重新创建一个Retrofit实例而已(详见RetrofitManager.kt
)。 - 动态请求头:通过自定义OkHttp Interceptor获取请求Url实现Request Headers传递(详见
OkHttpRequestHeaderInterceptor.kt
)。 - 自定义数据加解密器:通过自定义OkHttp Interceptor同时暴露ICipher接口使调用方灵活自定义请求/响应数据加解密器(详见
OkHttpRequestEncryptInterceptor.kt
、OkHttpResponseDecryptInterceptor.kt
、DefaultCipher.kt
)。 - 自定义数据解析器:通过反射获取Parser实例,获取到Parser实例后会保存到Map方便下一次获取。同时暴露IParser接口使调用方灵活自定义数据解析器(详见
AbstractRequestManager.kt
、DefaultParser.kt
)。
- Retrofit多实例管理:采用Map保存多个Retrofit实例,
-
Java泛型擦除问题
大家应该有遇到过,在Java中无法传递ArrayList.class。在Kotlin中,可以通过inline及reified关键字获取泛型T class,但在Java中会存在泛型擦除的问题(关于Java泛型擦除大家可自行了解,在此不再展开),为了解决此问题,通过自定义ParameterizedTypeImpl
实现ParameterizedType
接口即可(详见TypeUtil.java
及Demo中BaseRepository.java
调用)。
参数及API说明
- RequestOptions
参数名称 | 说明 | 类型 | 示例 | 默认值 | 备注 |
---|---|---|---|---|---|
requestMethod | 请求方式 | RequestMethod | RequestMethod.GET | RequestMethod.GET | / |
baseUrl | 服务器域名 | String | api.oick.cn/ | / | / |
function | 接口地址 | String | article/list/0/json | / | / |
headers | 请求头 | ArrayMap<String, Any> | / | / | / |
params | 请求参数 | ArrayMap<String, Any> | / | / | / |
contentType | 内容类型 | String | application/json; charset=utf-8 | application/json; charset=utf-8 | / |
- ShineOptions
参数名称 | 说明 | 类型 | 示例 | 默认值 | 备注 |
---|---|---|---|---|---|
logEnable | Shine日志开关 | Boolean | true | true | / |
logTag | Shine日志TAG | String | Custom | Shine | / |
baseUrl | Shine默认服务器域名 | String | / | / | 配置后,当某个接口没有动态设置BaseUrl时,将会用此默认BaseUrl |
parserCls | Shine默认数据解析器 | KClass | DefaultParser::class | DefaultParser::class | 配置后,当某个接口没有动态设置Parser时,将会用此默认Parser |
- IRequest
/**
* 抽象的接口请求封装,自定义RequestManager实现此接口即可
*
* @author: FreddyChen
* @date : 2022/01/07 13:47
* @email : freddychencsc@gmail.com
*/
interface IRequest
/**
* 异步请求
* @param options 请求参数
* @param type 数据类型映射
* @param parserCls 数据解析器
* @param cipherCls 数据加解密器
*/
suspend fun <T> request(
options: RequestOptions,
type: Type,
parserCls: KClass<out IParser>,
cipherCls: KClass<out ICipher>? = null
): T
/**
* 同步请求
* @param options 请求参数
* @param type 数据类型映射
* @param parserCls 数据解析器
* @param cipherCls 数据加解密器
*/
fun <T> syncRequest(
options: RequestOptions,
type: Type,
parserCls: KClass<out IParser>,
cipherCls: KClass<out ICipher>? = null
): T
- ICipher
/**
* 加解密器抽象接口
*
* @see [DefaultCipher]
* @author: FreddyChen
* @date : 2022/01/13 16:07
* @email : freddychencsc@gmail.com
*/
interface ICipher
/**
* 加密数据
*/
fun encrypt(original: String?): String?
/**
* 解密数据
*/
fun decrypt(original: String?): String?
/**
* 获取加解密字段名称
*/
fun getParamName(): String
- IParser
/**
* 数据解析器抽象接口
*
* @see [DefaultParser]
* @author: FreddyChen
* @date : 2022/01/06 17:53
* @email : freddychencsc@gmail.com
*/
interface IParser
fun<T> parse(url: String, data: String, type: Type): T
- IApiService
/**
* 统一的请求方式
* @author: FreddyChen
* @date : 2022/01/07 11:08
* @email : freddychencsc@gmail.com
*/
internal interface IApiService
/**
* 异步GET请求
* 无参
*/
@GET
suspend fun get(@Url function: String): String
/**
* 异步GET请求
* 带参
*/
@GET
suspend fun get(@Url function: String, @QueryMap params: ArrayMap<String, Any?>): String
/**
* 异步POST请求
* 无参
*/
@POST
suspend fun post(@Url function: String): String
/**
* 异步POST请求
* 带参
*/
@POST
suspend fun post(@Url function: String, @Body body: RequestBody): String
/**
* 异步PUT请求
* 无参
*/
@PUT
suspend fun put(@Url function: String): String
/**
* 异步PUT请求
* 带参
*/
@PUT
suspend fun put(@Url function: String, @Body body: RequestBody): String
/**
* 异步DELETE请求
* 无参
*/
@DELETE
suspend fun delete(@Url function: String): String
/**
* 异步DELETE请求
* 带参
*/
@DELETE
suspend fun delete(@Url function: String, @QueryMap params: ArrayMap<String, Any?>): String
/**
* 同步GET请求
* 无参
*/
@GET
fun syncGet(@Url function: String): Call<String>
/**
* 同步GET请求
* 带参
*/
@GET
fun syncGet(@Url function: String, @QueryMap params: ArrayMap<String, Any?>): Call<String>
/**
* 同步POST请求
* 无参
*/
@POST
fun syncPost(@Url function: String): Call<String>
/**
* 同步POST请求
* 带参
*/
@POST
fun syncPost(@Url function: String, @Body body: RequestBody): Call<String>
/**
* 同步PUT请求
* 无参
*/
@PUT
fun syncPut(@Url function: String): Call<String>
/**
* 同步PUT请求
* 带参
*/
@PUT
fun syncPut(@Url function: String, @Body body: RequestBody): Call<String>
/**
* 同步DELETE请求
* 无参
*/
@DELETE
fun syncDelete(@Url function: String): Call<String>
/**
* 同步DELETE请求
* 带参
*/
@DELETE
fun syncDelete(@Url function: String, @QueryMap params: ArrayMap<String, Any?>): Call<String>
使用方式
- 添加依赖
- Java
implementation "io.github.freddychen:shine-java:$lastest_version"
- Kotlin
implementation "io.github.freddychen:shine-kotlin:$lastest_version"
Note:最新版本可在maven central shine中找到。
- 初始化
使用Shine前进行初始化,建议放到Application#onCreate()。
val options = ShineOptions.Builder()
.setLogEnable(true)
.setLogTag("FreddyChen")
.setBaseUrl("https://api.oick.cn/")
.setParserCls(CustomParser1::class)
.build()
ShineKit.init(options)
当然,初始化不是强制的,ShineOptions会有对应的默认值,默认值可参考参数及API说明#ShineOptions
- 使用
suspend fun fetchCatList(): ArrayList<Cat>
val options = RequestOptions.Builder()
.setRequestMethod(RequestMethod.GET)
.setBaseUrl("https://cat-fact.herokuapp.com/")
.setFunction("facts/random?amount=2&animal_type=cat")
.build()
val type = object : TypeToken<ArrayList<Cat>>() .type
return ShineKit.getRequestManager().request(
options = options,
type = type,
parserCls = CustomParser1::class
)
当然,Type及Parser参数传递我们可以利用Kotlin特性封装一个通用的请求方法,这些大家根据自己的业务情况来选择就好,下面提供一个示例:
/**
* 异步请求
*/
suspend inline fun <reified T> request(
requestMethod: RequestMethod,
baseUrl: String = "https://api.oick.cn/",
function: String,
headers: ArrayMap<String, Any?>? = null,
params: ArrayMap<String, Any?>? = null,
contentType: String = NetworkConfig.DEFAULT_CONTENT_TYPE,
parserCls: KClass<out IParser> = CustomParser1::class,
cipherCls: KClass<out ICipher>? = null
): T
val optionsBuilder = RequestOptions.Builder()
.setRequestMethod(requestMethod)
.setBaseUrl(baseUrl)
.setFunction(function)
.setContentType(contentType)
if (!headers.isNullOrEmpty())
optionsBuilder.setHeaders(headers)
if (!params.isNullOrEmpty())
optionsBuilder.setParams(params)
return ShineKit.getRequestManager()
.request(optionsBuilder.build(), object : TypeToken<T>() .type, parserCls, cipherCls)
这样的话,上面的请求可以简化为:
suspend fun fetchCatList(): ArrayList<Cat>
return request(
requestMethod = RequestMethod.GET,
baseUrl = "https://cat-fact.herokuapp.com/",
function = "facts/random?amount=2&animal_type=cat",
)
- 示例
- 获取历史列表数据
服务器域名 | 接口地址 | 参数 | 返回数据结构 | 备注 |
---|---|---|---|---|
api.oick.cn/ | lishi/api.php | / | code、day、result | / |
例:
"code":"1",
"day":"01/ 17",
"result":[
"date":"395年01月17日",
"title":"罗马帝国分裂为西罗马帝国和东罗马帝国"
]
调用方式:
suspend fun fetchHistoryList(): ArrayList<History>
return request(
requestMethod = RequestMethod.POST,
function = "lishi/api.php",
)
- 获取新闻列表数据
服务器域名 | 接口地址 | 参数 | 返回数据结构 | 备注 |
---|---|---|---|---|
is.snssdk.com/ | api/news/feed/v51/ | / | message、data | / |
例:
"message":"success",
"data":[
"content":"test"
]
调用方式:
suspend fun fetchJournalismList(): ArrayList<Journalism>
return request(
requestMethod = RequestMethod.GET,
baseUrl = "https://is.snssdk.com/",
function = "api/news/feed/v51/",
parserCls = CustomParser2::class,
)
Note:如有业务需求使用同步请求方式,只需要把request()
方法改成syncRequest()
方法即可。
版本记录
版本号 | 修改时间 | 版本说明 |
---|---|---|
0.0.7 | 2022.01.16 | 首次提交 |
0.0.8 | 2022.02.15 | 修改minSdkVersion为19 |
免费开放的Api
提供两个免费开放Api平台给大家,方便测试:
写在最后
终于写完了,网络请求基本是每个android应用必须用到的组件,Shine为平时工作中的积累,也算是一种总结,希望对大家有所帮助。由于水平有限,也许Shine并不是最好的封装方式,开源这个项目,旨在起到抛砖引玉的作用,欢迎大家star和fork,让我们为Android开发共同贡献一份力量。
以上是关于Shine——更简单的Android网络请求库封装的主要内容,如果未能解决你的问题,请参考以下文章
android -------- OkGo (让网络请求更简单的框架)