写在前面
距离上一篇文章跟我一起开发商业级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提供的错误信息对业务做异常处理即可。
参数及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 : [email protected]
*/
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 : [email protected]
*/
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 : [email protected]
*/
interface IParser {
fun<T> parse(url: String, data: String, type: Type): T
}
复制代码
- IApiService
/**
* 统一的请求方式
* @author: FreddyChen
* @date : 2022/01/07 11:08
* @email : [email protected]
*/
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 | 首次提交 |
免费开放的Api
提供两个免费开发Api平台给大家,方便测试:
写在最后
终于写完了,网络请求基本是每个Android应用必须用到的组件,Shine为平时工作中的积累,也算是一种总结,希望对大家有所帮助。由于水平有限,也许Shine并不是最好的封装方式,开源这个项目,旨在起到抛砖引玉的作用,欢迎大家star和fork,让我们为Android开发共同贡献一份力量。
GitHub地址: